Load libraries

Loading required package: Matrix
Loading required package: igraph
package ‘igraph’ was built under R version 3.5.3
Attaching package: ‘igraph’

The following objects are masked from ‘package:stats’:

    decompose, spectrum

The following object is masked from ‘package:base’:

    union


Attaching package: ‘dplyr’

The following objects are masked from ‘package:igraph’:

    as_data_frame, groups, union

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union


Attaching package: ‘conos’

The following objects are masked _by_ ‘.GlobalEnv’:

    plotClusterBarplots, plotComponentVariance, plotDEheatmap

Loading required package: ggplot2
Learn more about the underlying theory at https://ggplot2-book.org/

Attaching package: ‘cowplot’

The following object is masked from ‘package:ggplot2’:

    ggsave

Custom clustering

#ncdcon$findCommunities(method=rleiden.community,r=c(1,0.5))
#ncdcon$findCommunities(method=leiden.community,r=1.2)

Load NB-only alignment

ncdcon <- readRDS("~pkharchenko/m/ninib/NB/ncdcon.rds")
doubletScores <- unlist(readRDS("~pkharchenko/m/ninib/NB/doubletScores.rds"))

Initial annotation

ninib_bannot <- readRDS("~pkharchenko/m/ninib/NB/ninib_bannot.rds")
annot <- readRDS("/d0-mendel/home/meisl/Workplace/neuroblastoma/cell.annotation.Jan2020.rds")$cellano

Clean up annotation

annot <- readRDS("/d0-mendel/home/meisl/Workplace/neuroblastoma/cell.annotation.Jan2020.rds")$cellano
annot <- setNames(as.character(annot),names(annot))
annot[annot=='unknown'] <- 'Th' # Ca2+ mediated apoptosis, involivng LCK, CD3D, RAC2, etc. Lumping in with Th 
annot[annot=='Plasmacytoid'] <- 'pDC'
annot[grep('Cytotoxic',annot)] <- 'Tcyto'
annot[grep('T helper',annot)] <- 'Th'
annot[grep('NK',annot)] <- 'NK'
annot[grep('Bcell',annot)] <- 'B'
annot[grep('PlasmaCell',annot)] <- 'Plasma'
annot[grep('Mast',annot)] <- 'Mast'
annot[grep("Bridge",annot)] <- 'SCP-like'
dannot <- as.factor(annot)

annot[grep("SOX11|Stress|euronal|Prolif",annot)] <- 'Noradrenergic'
annot[grep("Bridge",annot)] <- 'SCP-like'
annot <- as.factor(annot);
# renamings
levels(annot) <- gsub("Noradrenergic","Adrenergic",levels(annot))

set up pallets

set.seed(3)
annot.pal <- setNames(sample(rainbow(length(levels(annot)),v=0.95,s=0.7)),levels(annot));
annot.palf <- function(n) return(annot.pal)
dannot.pal <- setNames(sample(rainbow(length(levels(dannot)),v=0.85,s=0.9)),levels(dannot));
x <- which(levels(dannot) %in% levels(annot)); dannot.pal[x] <- annot.pal[match(levels(dannot)[x],levels(annot))]
dannot.palf <- function(n) return(dannot.pal)

# for samples
sample.pal <- setNames(sample(rainbow(length(ncdcon$samples),v=0.7,s=0.9)),names(ncdcon$samples))
sample.pal <- sample.pal[c(sort(names(sample.pal))[-1],'Adr')]
sample.palf <- function(n) return(sample.pal[1:n])
sample.palf <- function(n) { browser(); return(sample.pal[1:n])}
p1 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=ncdcon$getDatasetPerCell(),plot.na=F,mark.groups=F,palette=sample.pal)
p2 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=annot,plot.na=F,mark.groups=T,palette=annot.palf)
#p3 <- ncdcon$plotGraph(alpha=0.03,size=0.5,plot.na=F,mark.groups=T,clustering='leiden')
p3 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=dannot,plot.na=F,mark.groups=T,palette=dannot.palf)
plot_grid(plotlist=list(p1,p2,p3),nrow=1)

write out p2 app

source("~/m/pavan/DLI/conp2.r")
cell.subset <- unique(c(sample(rownames(ncdcon$embedding),2e3),names(annot)[annot=='SCP-like']))
ncdcon.p2 <- p2app4conos(ncdcon,file='ncdcon.bin',metadata = list(coarse=annot,detailed=dannot,cnv=cnv.sc),cell.subset = cell.subset)

Sample panel:

pp <- ncdcon$plotPanel(groups=annot,plot.na=F,alpha=0.1,size=0.2,use.common.embedding = T, ncol=2, mark.groups=F,palette=annot.pal,raster=T, raster.width=1,raster.height=1,title.size=4)
#pdf('panel.pdf',height=8,width=2); print(pp); dev.off();
pp

ncdcon$plotGraph(gene='TOP2A',alpha=0.5,size=0.1)

ncdcon$plotGraph(gene='TOP2A',alpha=0.5,size=0.1)
source("~/m/pavan/DLI/conp2.r")
ncdcon.p2 <- p2app4conos(ncdcon,file='ncdcon.bin',max.cells=1e4,metadata = list(annot=as.factor(annot),dannot=as.factor(dannot)))

Marker genes:

nfac <- annot;
nfac.de <- ncdcon$getDifferentialGenes(groups=nfac,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
source("~/m/p2/conos/R/plot.R")
pp <- plotDEheatmap(ncdcon,nfac,nfac.de,n.genes.per.cluster = 20 ,show.gene.clusters=T,column.metadata=list(samples=ncdcon$getDatasetPerCell()),order.clusters = T,use_raster=F, column.metadata.colors = list(clusters=annot.pal,samples=sample.pal))
pdf(file='nfac.heatmap2.pdf',width=10,height=30); print(pp); dev.off();

A small version of the hetmap, for the main figure

source("~/m/p2/conos/R/plot.R")
genes <- c("CD79B",'MS4A1','PLVAP','EPCAM','SPINK2','IL7R','LYZ','C1QB','CLEC10A','COL1A1','LUM','S100A9','IL1B','MYL9','GNLY','RTN1','MDK','NNAT','IRF4','MYH11','FCRL5','PLP1','GZMA','RORA','TIGIT')
pp <- plotDEheatmap(ncdcon,nfac,nfac.de,n.genes.per.cluster = 20 ,show.gene.clusters=T,column.metadata=list(samples=ncdcon$getDatasetPerCell()), column.metadata.colors = list(clusters=annot.pal,samples=sample.pal), order.clusters = T, additional.genes = genes, labeled.gene.subset = genes, use_raster=F,min.auc = 0.79)
pp

pdf(file='markers.small.pdf',width=6,height=6); print(pp); dev.off();

Make a very simplified version

sannot <- setNames(as.character(annot),names(annot));
sannot[grep("^T|ILC3|NK",sannot)] <- 'T cells'
sannot[grep("^B$|Plasma|pDC",sannot)] <- 'B cells'
sannot[grep("Macrop|Mono|mDC|Mast",sannot)] <- 'Myeloid'
sannot <- as.factor(sannot)

sannot.pal <- setNames(annot.pal[match(levels(sannot),names(annot.pal))],levels(sannot))
sannot.pal['T cells'] <- annot.pal['Tcyto']
sannot.pal['B cells'] <- annot.pal['B']
sannot.pal['Myeloid'] <- annot.pal['Macrophages']
sannot.de[['Noradrenergic']] %>% arrange(-AUC) %>% head(30)
sannot.de[['Adrenergic']] %>% arrange(-AUC) %>% head(30)
source("~/m/p2/conos/R/plot.R")
pp <- plotDEheatmap(ncdcon,sannot,sannot.de,n.genes.per.cluster = 20 ,show.gene.clusters=T,column.metadata=list(samples=ncdcon$getDatasetPerCell()),order.clusters = T, column.metadata.colors = list(clusters=sannot.pal,samples=sample))
pdf(file='sannot.heatmap.pdf',width=10,height=30); print(pp); dev.off();

A small version of the hetmap, for the main figure

source("~/m/p2/conos/R/plot.R")
genes <- c("CD79B",'MS4A1','PLVAP','EPCAM','SPINK2','IL7R','LYZ','C1QB','CLEC10A','COL1A1','LUM','S100A9','IL1B','MYL9','GNLY','RTN1','MDK','NNAT','IRF4','MYH11','FCRL5','PLP1','GZMA','RORA','TIGIT')
genes <- c("CD3D",'CD7','CD79A','HLA-DRA','CD74','LYZ','STMN2','MDK','MYL9','MYH11','PECAM1','LUM','PLP1')
tf <- sannot[unlist(tapply(1:length(sannot),sannot,function(x) { if(length(x)>2e2) x <- sample(x,2e2); x}))]
pp <- plotDEheatmap(ncdcon,tf,sannot.de,n.genes.per.cluster = 20 ,show.gene.clusters=T,column.metadata=list(samples=ncdcon$getDatasetPerCell()), column.metadata.colors = list(clusters=sannot.pal,samples=sample.pal), order.clusters = T, additional.genes = genes, labeled.gene.subset = genes, min.auc = 0.75,use_raster = T,raster_device = "CairoPNG",averaging.window=5)
pp

pdf(file='sannot.select.pdf',width=4,height=6); print(pp); dev.off();
source("~/m/p2/conos/R/plot.R")
pdf(file='sannot.select.pdf',width=5,height=6); 
plotDEheatmap(ncdcon,sannot,sannot.de,n.genes.per.cluster = 20 ,show.gene.clusters=T,column.metadata=list(samples=ncdcon$getDatasetPerCell()), column.metadata.colors = list(clusters=sannot.pal,samples=sample.pal), order.clusters = T, additional.genes = genes, labeled.gene.subset = genes, min.auc = 0.75)
dev.off();
pdf(file='sannot.small.pdf',width=6,height=6); print(pp); dev.off();
null device 
          1 
pdf(file='sannot.small.pdf',width=6,height=6); print(pp); dev.off();

Draw just the tumor cell annotations:

source("~/m/p2/conos/R/plot.R")
tf <- droplevels(sannot[grep("Adr|SCP|Mesench",sannot)]); tf <-factor(tf,levels=levels(tf)[c(2,3,1)])
pp <- plotDEheatmap(ncdcon,tf,sannot.de,n.genes.per.cluster = 10, expression.quantile=0.95, show.gene.clusters=T,column.metadata=list(samples=ncdcon$getDatasetPerCell()),order.clusters = F, column.metadata.colors = list(clusters=sannot.pal,samples=sample.pal),additional.genes = c("SOX10","PRRX1", "LEPR", "PDGFRA","TH","ISL1","NRG1","ERBB3"),exclude.genes=c("CRYAB"),min.auc=0.75,row.label.font.size = 10,split=T, split.gap=1,column_title = NULL,use_raster = T,raster_device = "CairoPNG",averaging.window=5)
pp

pdf(file='NB.markers.small.pdf',width=4,height=6); print(pp); dev.off();
null device 
          1 
ncdcon$plotGraph(alpha=0.3,size=0.5,plot.na=F,mark.groups=T,gene='RORA')

Plot CNV scores

ncdcon$plotPanel(gene='PLP1',alpha=1,size=0.3, use.common.embedding = T,ncol=2,gradient.range.quantile=0.8)

See interaction between PLP1 expression in the ‘bridge’ cell population and the CNV scores across different samples


#exp <- pmin(conos:::getGeneExpression(ncdcon,'PLP1'),conos:::getGeneExpression(ncdcon,'S100B'))

exp <- conos:::getGeneExpression(ncdcon,'PLP1')
samplef <- ncdcon$getDatasetPerCell()

pl <- lapply(names(cutoff.list),function(san) {
  cn <- intersect(names(annot)[annot=='SCP-like'],names(samplef)[samplef==san])
  df <- data.frame(cnv=cnv.sc[match(cn,names(cnv.sc))],exp=exp[match(cn,names(exp))],row.names=cn)
  #ggplot(df,aes(exp,cnv)) + geom_point(col=adjustcolor(1,alpha=0.3)) + ggtitle(san) +geom_hline(yintercept = 0, linetype="dashed", color = "red")
  #ggplot(df,aes(exp,cnv)) + geom_point(col=adjustcolor(pagoda2:::val2col(doubletScores[cn],zlim=c(0,0.5),gradientPalette=colorRampPalette(c('black','red'))(1024)),alpha=0.3)) + ggtitle(san) +geom_hline(yintercept = 0, linetype="dashed", color = "red")
  ggplot(df,aes(exp,cnv)) + geom_point(col=adjustcolor(ifelse(doubletScores[match(cn,names(doubletScores))]>0.2,'red','black'),alpha=0.3)) + ggtitle(san) +geom_hline(yintercept = 0, linetype="dashed", color = "red")
})

plot_grid(plotlist=pl)

Just the scores

cnv.sc <- readRDS("/d0-mendel/home/meisl/Workplace/neuroblastoma/AmpScore.rds")

Stringent scores:

load("/d0-mendel/home/meisl/Workplace/neuroblastoma/Figures/S1/CNV/FDR5.RData")
names(cutoff.list) <- gsub("\\..*","",names(cutoff.list))
cutoff.list <- setNames(unlist(cutoff.list),names(cutoff.list))

valid.samples <- c('NB12','NB26')
#valid.samples <- unique(gsub("_.*","",names(cnv.sc)));
cnv.sc <- cnv.sc[ncdcon$getDatasetPerCell()[names(cnv.sc)] %in% valid.samples]

Detailed info allScoreA: scaled gene set average expression. cutoff.list: cutoff for each patient CNV.cells: amplified cell name

load("/d0-mendel/home/meisl/Workplace/neuroblastoma/Figures/CNV/CNV.0211.RData")
names(cutoff.list) <- gsub("\\..*","",names(cutoff.list))
cutoff.list <- setNames(unlist(cutoff.list),names(cutoff.list))

Calculate a scaled score


cell.cnv.thresholds <- setNames(cutoff.list[gsub("_.*","",names(allScoreA))],names(allScoreA))
cnv.sc <- allScoreA-cell.cnv.thresholds

Limit to the samples where we see tumor cells and CNVs

valid.samples <- c('NB01','NB09','NB12','NB13','NB15','NB18','NB19','NB20','NB21','NB24','NB26')
cnv.sc <- cnv.sc[ncdcon$getDatasetPerCell()[names(cnv.sc)] %in% valid.samples]
hist(cnv.sc,col='wheat')

emb <-ncdcon$embedding
ncdcon$embedding <- emb[names(cnv.sc)[order(cnv.sc)],]
col <- pagoda2:::val2col(cnv.sc,gradient.range.quantile = 0.9,gradientPalette = colorRampPalette(c(rep('grey85',3),'red'))(1024))
p3 <- ncdcon$plotGraph(alpha=0.1,size=0.5,colors=col,plot.na=F)
ncdcon$embedding <- emb;
p3

Threshold-based, with overplotting

col <- cnv.sc>0.7;
#col <- cnv.sc>0.8e-3; 
emb <-ncdcon$embedding
ncdcon$embedding <- emb[names(col)[order(col)],]
p3 <- ncdcon$plotGraph(alpha=0.05,size=0.6,groups=col,mark.groups=F,raster=T,palette=c("TRUE"='red',"FALSE"='gray90'),plot.na=F,raster.height=4,raster.width=4)
ncdcon$embedding <- emb;
#pdf(file='cnv_overview.pdf',width=4,height=4); print(p3); dev.off();
p3

Combined figure

p1 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=ncdcon$getDatasetPerCell(),plot.na=F,mark.groups=F,palette=sample.pal)
p2 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=annot,plot.na=F,mark.groups=T,palette=annot.palf)
#p3 <- ncdcon$plotGraph(alpha=0.03,size=0.5,plot.na=F,mark.groups=T,clustering='leiden')
#p3 <- ncdcon$plotGraph(alpha=0.03,size=0.5,colors=pagoda2:::val2col(cnv.sc,gradient.range.quantile = 0.9),plot.na=F)
pl <- list(p1,p2,p3)
plot_grid(plotlist=pl,nrow=1)

require(gridExtra)
Loading required package: gridExtra

Attaching package: ‘gridExtra’

The following object is masked from ‘package:dplyr’:

    combine
raster <- T; size=0.5; alpha=0.03;
p1 <- ncdcon$plotGraph(alpha=alpha,size=size,raster=raster,groups=ncdcon$getDatasetPerCell(),plot.na=F,mark.groups=F,palette=sample.pal,raster.height=5,raster.width=5)
p2 <- ncdcon$plotGraph(alpha=alpha,size=size,raster=raster,groups=annot,plot.na=F,mark.groups=T,palette=annot.palf,font.size=c(3,5),raster.height=5,raster.width=5)
#p3 <- ncdcon$plotGraph(alpha=alpha,size=size,raster=raster,colors=col,plot.na=F)
pl <- list(p1,p2,p3)
pdf(file='overview.pdf',width=4,height=6); 
grid.arrange(p2,p1,p3,layout_matrix=rbind(c(1,1),c(2,3)),heights=c(2,1))
dev.off();
null device 
          1 
invisible(lapply(1:length(pl),function(i) { pdf(paste('panel',i,'pdf',sep='.'),width=4,height=4); print(pl[[i]]); dev.off()}))

Looking at cells with SCP-like pluripotency genes

ascl1e <- conos:::getGeneExpression(ncdcon,'ASCL1')
phox2be <- conos:::getGeneExpression(ncdcon,'PHOX2B')
prrx2e <- conos:::getGeneExpression(ncdcon,'PRRX2')
prrx1e <- conos:::getGeneExpression(ncdcon,'PRRX1')
sox10e <- conos:::getGeneExpression(ncdcon,'SOX10')
foxd3e <- conos:::getGeneExpression(ncdcon,'FOXD3')

dc <- sox10e>0 & phox2be>0
#dc <- sox10e>0 & ascl1e>0
dc <- sox10e>0 & phox2be>0
dc <- foxd3e>0 & phox2be>0
dc <- sox10e>0 & prrx2e>0
dc <- sox10e>0 & prrx1e>0
dc <- phox2be>0 & prrx1e>0
dc <- phox2be>0.3 & prrx1e>0.3 & sox10e>0
table(dc)
names(dc)[dc]
em <- ncdcon$samples$NB26$embeddings$PCA[[2]]
plot(em[,1],em[,2],pch=19,cex=0.2,col=adjustcolor(1,alpha=0.01))
vc <- names(dc)[dc]
points(em[rownames(em) %in% vc,1],em[rownames(em) %in% vc,2],col=2)

Look at scatter plots of key fork markers

gns <- c('SOX10','ASCL1','PHOX2B','PRRX2','PRRX1','FOXD3','POSTN')
gnse <- lapply(pagoda2:::sn(gns),function(g) conos:::getGeneExpression(ncdcon,g))
g1 <- 'SOX10'; g2 <-'PHOX2B'
g1 <- 'POSTN'; g2 <-'PHOX2B'
#g1 <- 'SOX10'; g2 <-'PRRX2' # 1 cell
#g1 <- 'SOX10'; g2 <-'PRRX1' # clear
#g1 <- 'SOX10'; g2 <-'ASCL1' # none
#g1 <- 'FOXD3'; g2 <- 'PHOX2B'
#g1 <- 'PRRX1'; g2 <- 'PHOX2B'

dc <- gnse[[g1]]>0 & gnse[[g2]]>0
vc <- names(dc)[dc]; vc <- vc[grepl('^NB',vc)]
df <- data.frame(gnse[[g1]][vc],gnse[[g2]][vc]); colnames(df) <- c(g1,g2)
df <- cbind(df,data.frame(type=annot[vc],cell=vc,doublet=doubletScores[vc]>0.2,inemb=vc %in% rownames(ncdcon$embedding)))
p1 <- ggplot(df,aes_(x=as.name(g1),y=as.name(g2),colour=quote(type))) + geom_point(size=2) + scale_color_manual(values=annot.pal)
p2 <- ggplot(df,aes_(x=as.name(g1),y=as.name(g2),colour=quote(doublet),shape=quote(inemb))) + geom_point() + scale_color_manual(values=c("FALSE"="gray30", "TRUE"="red"));
cowplot::plot_grid(plotlist=list(p1,p2))

Make a panel of pairs

pl <- list(c('SOX10','PHOX2B'),c('SOX10','PRRX1'),c('SOX10','PRRX2'),c('PRRX1','PHOX2B'),c('POSTN','PHOX2B'))
pp <- lapply(pl,function(x) {
  g1 <- x[1]; g2 <- x[2]
  dc <- gnse[[g1]]>0 & gnse[[g2]]>0
  vc <- names(dc)[dc]; vc <- vc[grepl('^NB',vc)]
  df <- data.frame(gnse[[g1]][vc],gnse[[g2]][vc]); colnames(df) <- c(g1,g2)
  df <- cbind(df,data.frame(type=annot[vc],cell=vc,doublet=doubletScores[vc]>0.2,inemb=vc %in% rownames(ncdcon$embedding)))
  p1 <- ggplot(df,aes_(x=as.name(g1),y=as.name(g2),colour=quote(type))) + geom_point(size=2) + scale_color_manual(values=annot.pal)
})
cowplot::plot_grid(plotlist=pp,ncol=2)

vc <- names(dc)[dc]; vc <- vc[grepl('^NB',vc)]
df <- data.frame(phox2b=phox2be[vc],prrx1=prrx1e[vc],type=annot[vc],cell=vc)
ggplot(df,aes(x=phox2b,y=prrx1,colour=type)) + geom_point()

dc <- phox2be>0 & prrx1e>0
dc <- phox2be>0 & prrx1e>0
dc <- phox2be>0.3 & prrx1e>0.3 & sox10e>0
vc <- names(dc)[dc]; vc <- vc[grepl('^NB',vc)]

p1 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=annot,plot.na=F,mark.groups=T)
p2 <- ncdcon$plotGraph(alpha=0.03,size=0.5,gene='SOX10',plot.na=F,mark.groups=T)
p3 <- ncdcon$plotGraph(alpha=0.2,size=3,groups=setNames(rep(1,length(vc)),vc),plot.na=F,mark.groups=F)+xlim(range(ncdcon$embedding[,1])) +ylim(range(ncdcon$embedding[,2]))
plot_grid(plotlist=list(p1,p2,p3),nrow=1)

Annotation of the adrenal data

neww_annot <- readRDS("~pkharchenko/m/ninib/NB/neww_annot.rds")
ap2 <- ncdcon$samples$Adr
ap2$getEmbedding(type='PCA',embeddingType='tSNE',n.cores=30)
ap2$getKnnClusters(type='PCA',method=leiden.community,r=1)
ap2$getKnnClusters(type='PCA',method=leiden.community,r=1)
ap2$plotEmbedding(type='PCA',embeddingType = 'tSNE',mark.clusters = T,cex=0.2,mark.cluster.cex = 1.5)

write out p2 app

suppressMessages(library(org.Hs.eg.db))
ids <- unlist(lapply(mget(colnames(ap2$counts),org.Hs.egALIAS2EG,ifnotfound=NA),function(x) x[1]))
rids <- names(ids); names(rids) <- ids;
# list all the ids per GO category                                                            
go.env <- list2env(eapply(org.Hs.egGO2ALLEGS,function(x) as.character(na.omit(rids[x]))))
ap2$testPathwayOverdispersion(go.env,verbose=T,correlation.distance.threshold=0.95,recalculate.pca=F,top.aspects=15)

library(GO.db)
termDescriptions <- Term(GOTERM[names(go.env)]); # saves a good minute or so compared to individual lookups                                                                         
sn <- function(x) { names(x) <- x; x}
geneSets <- lapply(sn(names(go.env)),function(x) {
  list(properties=list(locked=T,genesetname=x,shortdescription=as.character(termDescriptions[x])),genes=c(go.env[[x]]))
})
adr.p2app <- make.p2.app(ap2, dendrogramCellGroups = ap2$clusters$PCA$multilevel, geneSets = geneSets,innerOrder='odPCA');
adr.p2app$serializeToStaticFast(binary.filename = 'adr.p2.app.bin',verbose=T)

Get markers

de <- ap2$getDifferentialGenes(type='PCA',n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
source("~/m/p2/conos/R/plot.R")
fac <- ap2$clusters$PCA[[1]]
pp <- plotDEheatmap(ap2,fac,de,n.genes.per.cluster = 10 ,show.gene.clusters=T,row.label.font.size = 6)
pdf(file='adr.heatmap.pdf',width=7,height=15); print(pp); dev.off();
pp
ncdconA <- readRDS("~pkharchenko/m/ninib/NB/ncdconA.rds")
cf <- unlist(list(as.factor(setNames(rep('NB',length(nfac)),names(nfac))),neww_annot))
p1 <- ncdconA$plotGraph(alpha=0.05,size=0.8,groups=cf,plot.na=F,font.size=3,palette=function(n) c('gray95',rainbow(n-1,v=1)))
fac <- ap2$clusters$PCA[[1]]
cf <- unlist(list(as.factor(setNames(rep('NB',length(nfac)),names(nfac))),fac))
p3 <- ncdconA$plotGraph(alpha=0.05,size=0.8,groups=cf,plot.na=F,font.size=3,palette=function(n) c('gray95',rainbow(n-1,v=1)))
p2 <- ncdconA$plotGraph(alpha=0.03,size=0.5,groups=nfac,plot.na=F,font.size=3)
plot_grid(plotlist=list(p1,p3,p2),nrow=1)

Linear deviation tests

p1 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=ncdcon$getDatasetPerCell(),plot.na=F,mark.groups=F,palette=sample.pal)
p2 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=annot,plot.na=F,mark.groups=T,palette=annot.palf)
#p3 <- ncdcon$plotGraph(alpha=0.03,size=0.5,plot.na=F,mark.groups=T,clustering='leiden')
p3 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=dannot,plot.na=F,mark.groups=T,palette=dannot.palf)
plot_grid(plotlist=list(p1,p2,p3),nrow=1)
df <- data.frame(ncdcon$embedding)
df$type <- annot[rownames(df)]
colnames(df) <- c('x','y','t')
ggplot(df,aes(x,y,color=t))+geom_point(size=0.1,alpha=0.1) + geom_vline(xintercept = c(-18,-13,-3,10),linetype=3)

cells <- intersect(names(annot)[annot %in% c("Mesenchymal",'SCP-like','Noradrenergic')], rownames(ncdcon$embedding)[ncdcon$embedding[,1] <10 & ncdcon$embedding[,1] > -13 & ncdcon$embedding[,2]< -17])
mes.cells <- intersect(names(annot)[annot %in% c("Mesenchymal",'SCP-like','Noradrenergic')], rownames(ncdcon$embedding)[ncdcon$embedding[,1] < -3 & ncdcon$embedding[,1] > -18 & ncdcon$embedding[,2]< -17]) 

Principal curve fit

fit.pc.pseudotime <- function(con, cells,ndims=10,embedding=NULL, return.details=F) {
  require(princurve)
  if(is.null(embedding)) {
    if(is.null(con$misc$embeddings) || is.null(con$misc$embeddings$linfit)) {
      old.emb <- con$embedding;
      embedding <- con$misc$embeddings$linfit <- con$embedGraph(method='largeVis',target.dims=ndims)
      con$embedding <- old.emb;
    } else {
      embedding <- con$misc$embeddings$linfit
    } 
  }
  embedding <- embedding[rownames(embedding) %in% cells,]
  x <- principal_curve(embedding)
  if(return.details) { 
    # identify closest cell for each principal point
    en <- conos:::n2CrossKnn(x$s,embedding,3,verbose=F,indexType='L2')
    colnames(en) <- names(x$lambda); rownames(en) <- rownames(embedding)
    en@x <- exp(mean(en@x)/en@x/10)
    en <- t(en)/colSums(en)
    x$en <- en;
    x$embedding <- embedding;
    return (x)
  }
  x$lambda
}
set.seed(0)
pcurve <- fit.pc.pseudotime(ncdcon,cells,ndims=5,return.details=T)
pt <- pcurve$lambda
pt.pos <- ncdcon$embedding[names(pt)[order(pt)],] %>% zoo::rollapply(500,median) %>% data.frame()
colnames(pt.pos) <- c('x','y');
pt.pos$sd <- ncdcon$embedding[names(pt)[order(pt)],] %>% zoo::rollapply(500,sd) %>% apply(1,sum)

Aspect ration difference between the selected subset and the actual one

Reduce(eval('/'),apply(ncdcon$embedding,2,function(x) diff(range(x))))/Reduce(eval('/'),apply(ncdcon$embedding[cells,],2,function(x) diff(range(x))))
# estimate principal curve positions
p1 <- ncdcon$plotGraph(alpha=0.2,size=0.2,colors=pt-mean(pt),plot.na=F,gradient.range.quantile=0.95) +
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0)) + 
  theme(panel.grid.major = element_blank())+
  geom_line(data=pt.pos[1:3.2e3,],aes(x,y),alpha=0.8,size=0.5) 
p1

Focused bridge and CNV picture for fig.2

p0 <- ncdcon$plotGraph(alpha=0.2,size=0.2,groups=annot[cells],palette=annot.pal,mark.groups=F,plot.na=F,gradient.range.quantile=0.95,raster=T,raster.width=1.5,raster.height=1.5) +
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0)) + 
  #geom_label(x=-Inf,y=Inf,vjust=1,hjust=0,label='clusters',data=data.frame(x=c(1)),label.size=0) +
  theme(panel.grid.major = element_blank())

col <- cnv.sc>0.7; 
emb <- ncdcon$embedding
ncdcon$embedding <- emb[names(col)[order(col)],]
p3 <- ncdcon$plotGraph(alpha=0.05,size=0.3,groups=col[cells],mark.groups=F,raster=T,palette=c("TRUE"='red',"FALSE"='gray90'),plot.na=F,raster.height=1.5,raster.width=1.5)+
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0)) + 
  geom_label(x=-Inf,y=-Inf,vjust=0,hjust=0,label='(CNV)',data=data.frame(x=c(1)),label.size=0) +
  theme(panel.grid.major = element_blank())
ncdcon$embedding <- emb;
#pdf(file='cnv_overview.pdf',width=4,height=4); print(p3); dev.off();
plot_grid(plotlist=list(p0,p3),ncol=1)

gl <- rev(c("S100B","SOX10"))
pl <- lapply(gl,function(g) ncdcon$plotGraph(colors=conos:::getGeneExpression(ncdcon,g)[cells],alpha=0.3,size=0.1,plot.na=F,gradient.range.quantile=0.99,raster=T,raster.width=1.5,raster.height=1.5)+
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0)) + 
  geom_label(x=-Inf,y=-Inf,vjust=0,hjust=0,label=g,data=data.frame(x=c(1)),label.size=0) +
  theme(panel.grid.major = element_blank()))
pp <- plot_grid(plotlist=c(list(p0,p3),pl),ncol=1)
pp

# returns normalized mean residuals per gene within the cel3 population, relative to a simple linear model mixing cel1 and cel2 populations
# cel1,cel2,cel3 - cell name vectors
# mat - matrix of molecule counts (raw counts - not normalized)
lin.dev <- function(cel1,cel2,cel3,mat) {
  mat <- mat[rownames(mat) %in% c(cel1,cel2,cel3),,drop=F]
  cf <- setNames(as.factor(rep(c('g1','g2'),c(length(cel1),length(cel2)))),c(cel1,cel2))
  # calculate aggregate count profiles
  cm <- conos:::collapseCellsByType(mat,cf)
  cm1 <- cm[1,]; cm2 <- cm[2,]
  # fit linear model on the bridge
  x <- as.matrix(t(mat[cel3,]))
  y <- lm(x~cm1+cm2-1)
  # report residuals
  #rs <- rstudent(y)
  # calculate pearson residual using sd from all three populations (instead of a standard studentized resiudal)
  rs <- residuals(y,'response')
  rss <- rowMeans(rs)
  rss <- rss/apply(mat,2,sd) # all three populations
  rss <- rss[is.finite(rss)]
  rss <- rss[order(rss,decreasing=T)]
}

# a convenience wrapper, starting with a pseudotime, conos object and the start/end region pseudotime ranges (reg1,reg2 are tuples of time values)
lin.dev.pt <- function(pt,con,reg1,reg2) {
  # determine cells associated with the two regions
  cel1 <- names(pt)[pt>=reg1[1] & pt<=reg1[2]]
  cel2 <- names(pt)[pt>=reg2[1] & pt<=reg2[2]]
  cel3 <- names(pt)[pt>reg1[2] & pt<reg2[1]]
  
  # construct a joint count matrix
  mat <- conos:::rawMatricesWithCommonGenes(con)
  mat <- do.call(rbind,lapply(mat,function(x) x[rownames(x) %in% c(cel1,cel2,cel3),,drop=F]))
  lin.dev(cel1,cel2,cel3,mat)
}

For illustration

For the noradrenergic side: PHOX2B, LMO1, TH For the mesenchymal side: PRRX1

rss3 <- lin.dev.pt(ptr,ncdcon,c(1.5e3,2.0e3),c(2.6e3,6e3))
gl <- rev(c("PHOX2B","PRRX1","SOX10"))
pl <- lapply(gl,function(g) ncdcon$plotGraph(colors=conos:::getGeneExpression(ncdcon,g)[cells],alpha=0.3,size=0.1,plot.na=F,gradient.range.quantile=0.99,raster=T,raster.width=1.5,raster.height=1.5)+
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0)) + 
  geom_label(x=-Inf,y=-Inf,vjust=0,hjust=0,label=g,data=data.frame(x=c(1)),label.size=0) +
  theme(panel.grid.major = element_blank()))
pp <- plot_grid(plotlist=c(list(p0),pl),ncol=1)
pp

pdf(file='nb.fork.genes2.pdf',width=1,height=1*0.94*4); print(pp); dev.off();
ncdcon$plotGraph(colors=conos:::getGeneExpression(ncdcon,'SOX10')[cells],alpha=0.3,size=0.1,plot.na=F,gradient.range.quantile=0.999,raster=T,raster.width=1.5,raster,height=1.5) +geom_label(x=-Inf,y=-Inf,vjust=0,hjust=0,label='SOX10',inherit.aes =F,data=data.frame(x=c(1)),label.size=0)

Test for associated genes

mat <- ncdcon$getJointCountMatrix()
mat <- t(mat[names(pt)[order(pt)[1:3.2e3]],])
# F test comparing glm models of expression with and without pseudotime
test.associated.genes <- function(pt,mat,spline.df=3,n.cores=32) {
  mat <- mat[,colnames(mat) %in% names(pt)]
  df <- data.frame(do.call(rbind,mclapply(setNames(1:nrow(mat),rownames(mat)),function(i) {
    exp <- mat[i,]
    sdf <- data.frame(exp=exp,t=pt[colnames(mat)])
    # model
    m <- mgcv::gam(exp~s(t,k=spline.df),data=sdf,familly=gaussian())
    # background
    m0 <- mgcv::gam(exp~1,data=sdf,familly=gaussian())
    fstat <- 0; 
    if(m$deviance>0) {
      fstat <- (deviance(m0) - deviance(m))/(df.residual(m0)-df.residual(m))/(deviance(m)/df.residual(m))
    }
    pval <-  pf(fstat,df.residual(m0)-df.residual(m),df.residual(m),lower.tail = FALSE);
    return(c(pval=pval,A=diff(range(predict(m)))))
  },mc.cores=n.cores,mc.preschedule=T)))
  df$gene <- rownames(mat)
  df$fdr <- p.adjust(df$pval);
  df
}
pt.genes <- test.associated.genes(pt,mat)
pt.genes <- arrange(pt.genes,pval)

Heatmaps

rescale.and.center <- function(x, center=F, max.quantile=0.99) {
  mx <- quantile(abs(x),max.quantile) # absolute maximum
  if(mx==0) mx<-max(abs(x)) # in case the quantile squashes all the signal
  x[x>mx] <- mx; x[x< -1*mx] <- -1*mx; # trim
  if(center) x <- x-mean(x) # center
  x/max(abs(x)); # scale
}
gns <- pt.genes$gene[pt.genes$fdr<1e-5 & pt.genes$A>1e-2]
gns <- pt.genes$gene[pt.genes$A>1e-2][1:1000]

#gns <- pt.genes$gene[pt.genes$fdr>1e-2 & pt.genes$A>1e-2]
gm <- zoo::rollapply(as.matrix(t(mat[rev(gns),])),30,mean,align='left',partial=T) %>% apply(2,rescale.and.center,max.quantile=1-1e-3) %>% t
colnames(gm) <- colnames(mat)
# cluster
#gm.hcl <- hclust(as.dist(2-cor(t(gm))),method='ward.D2')
gm.hcl <- hclust(dist(gm),method='ward.D2')
gm.hcl.clust <- cutree(gm.hcl,40)
gm <- gm[gm.hcl$order,]
require(ComplexHeatmap)
ha = HeatmapAnnotation(
  cells=annot[colnames(gm)],  
  #patient=ncdcon$getDatasetPerCell()[colnames(gm)],
  # specify colors
  col = list(cells=annot.pal,
             patient=sample.pal
             #pseudotime=circlize::colorRamp2(c(-1, 0, 1), c('darkgreen','grey90','orange'))
  ),
  border = T
)
ra <- ComplexHeatmap::HeatmapAnnotation(df=data.frame(clust=as.factor(gm.hcl.clust[rownames(gm)])),which='row',show_annotation_name=FALSE, show_legend=FALSE, border=T)

hm <- Heatmap(gm, cluster_columns = F, cluster_rows = F,show_column_names = F,border=T, top_annotation = ha, name='expression', show_heatmap_legend = F, show_row_dend = F, show_row_names=F, left_annotation = ra)
labeled.genes <- rownames(gm)[round(seq(1,nrow(gm),length.out = 70))]; 
hm + ComplexHeatmap::rowAnnotation(link = ComplexHeatmap::anno_mark(at = match(labeled.genes,rownames(gm)), labels = labeled.genes, labels_gp = grid::gpar(fontsize = 7)))
gns2 <- c('PHOX2B','RGS5','MLLT11',"STMN2", 'PCBP2', 'MDK','HMGB1','SRP14', #'CALM2',
         #'NLRP1',
         'ABCA8','FXYD1','CDH19','ERBB3','S100B','SOX10','MPZ','S100A10','B2M','IFITM3', # 'CNN3',
         'PODXL','NPNT', 'TNNT2','FGF1',
         'MAFF','KLF4','MYC','FOSB',
         'PRRX1','CALD1','LUM','PDGFRA','COL1A2','BGN')
#gns2 <- rownames(gm)[Reduce(seq,match(c("ANXA1","PSAP"),rownames(gm)))]
#gns2 <- rownames(gm)[Reduce(seq,match(c("TMSB15A","APC"),rownames(gm)))]
gm2 <- zoo::rollapply(as.matrix(t(mat[rev(gns2),])),30,mean,align='left',partial=T) %>% apply(2,rescale.and.center,max.quantile=1-5e-3) %>% t
#gm <- apply(mat[gns,],1,rescale.and.center) %>% zoo::rollapply(10,mean,align='left',partial=T) %>% t
colnames(gm2) <- colnames(mat)
require(ComplexHeatmap)
ha = HeatmapAnnotation(
  cells=annot[colnames(gm2)],  
  #patient=ncdcon$getDatasetPerCell()[colnames(gm2)],
  # specify colors
  col = list(cells=annot.pal,
             patient=sample.pal
             #pseudotime=circlize::colorRamp2(c(-1, 0, 1), c('darkgreen','grey90','orange'))
  ),
  border = T
)

hm2 <- Heatmap(gm2, cluster_columns = F, cluster_rows = F,show_column_names = F,border=T, top_annotation = ha, name='expression', show_heatmap_legend = F, show_row_dend = F, row_names_gp = grid::gpar(fontsize = 8),use_raster=T, raster_device = "CairoPNG")
hm2

pdf(file='small.bridge.heatmap.pdf',width=4,height=4); hm2; dev.off()
null device 
          1 
Cairo::CairoPNG(file='small.bridge.heatmap.png',width=400,height=400); hm2; dev.off()
null device 
          1 

Combined large heatmap

gns <- pt.genes$gene[pt.genes$A>1e-2][1:1000]
gns <- unique(c(gns,gns2))
gm <- zoo::rollapply(as.matrix(t(mat[rev(gns),])),30,mean,align='left',partial=T) %>% apply(2,rescale.and.center,max.quantile=1-1e-3) %>% t
colnames(gm) <- colnames(mat)
# cluster
#gm.hcl <- hclust(as.dist(2-cor(t(gm))),method='ward.D2')
gm.hcl <- hclust(dist(gm),method='ward.D2')
gm.hcl.clust <- cutree(gm.hcl,40)
gm <- gm[gm.hcl$order,]
require(ComplexHeatmap)
ha = HeatmapAnnotation(
  cells=annot[colnames(gm)],  
  #patient=ncdcon$getDatasetPerCell()[colnames(gm)],
  # specify colors
  col = list(cells=annot.pal,
             patient=sample.pal
             #pseudotime=circlize::colorRamp2(c(-1, 0, 1), c('darkgreen','grey90','orange'))
  ),
  border = T
)
ra <- ComplexHeatmap::HeatmapAnnotation(df=data.frame(clust=as.factor(gm.hcl.clust[rownames(gm)])),which='row',show_annotation_name=FALSE, show_legend=FALSE, border=T)

hm <- Heatmap(gm, cluster_columns = F, cluster_rows = F,show_column_names = F,border=T, top_annotation = ha, name='expression', show_heatmap_legend = F, show_row_dend = F, show_row_names=F,use_raster=T, raster_device = "CairoPNG")
labeled.genes <- rownames(gm)[round(seq(1,nrow(gm),length.out = 70))]; #
labeled.genes <- gns2;
hm <- hm + ComplexHeatmap::rowAnnotation(link = ComplexHeatmap::anno_mark(at = match(labeled.genes,rownames(gm)), labels = labeled.genes, labels_gp = grid::gpar(fontsize = 7)))
hm
pdf(file='full.bridge.heatmap.pdf',width=3.5,height=5); hm; dev.off()
CairoPNG(file='full.bridge.heatmap.png',width=350,height=500); hm; dev.off()

Pseudotime tests

ptr <- rank(pt) # work on ranks - they're more stable
df <- data.frame(pt=ptr,annot=annot[names(ptr)])
ggplot(df,aes(x=pt,color=annot)) + geom_density() + geom_vline(xintercept = c(1.2e3,1.5e3,2.0e3,2.6e3))

Test for deviations from linear interpolation between two clusters on the principal curve

# returns normalized mean residuals per gene within the cel3 population, relative to a simple linear model mixing cel1 and cel2 populations
# cel1,cel2,cel3 - cell name vectors
# mat - matrix of molecule counts (raw counts - not normalized)
lin.dev <- function(cel1,cel2,cel3,mat) {
  mat <- mat[rownames(mat) %in% c(cel1,cel2,cel3),,drop=F]
  cf <- setNames(as.factor(rep(c('g1','g2'),c(length(cel1),length(cel2)))),c(cel1,cel2))
  # calculate aggregate count profiles
  cm <- conos:::collapseCellsByType(mat,cf)
  cm1 <- cm[1,]; cm2 <- cm[2,]
  # fit linear model on the bridge
  x <- as.matrix(t(mat[cel3,]))
  y <- lm(x~cm1+cm2-1)
  # report residuals
  #rs <- rstudent(y)
  # calculate pearson residual using sd from all three populations (instead of a standard studentized resiudal)
  rs <- residuals(y,'response')
  rss <- rowMeans(rs)
  rss <- rss/apply(mat,2,sd) # all three populations
  rss <- rss[is.finite(rss)]
  rss <- rss[order(rss,decreasing=T)]
}

# a convenience wrapper, starting with a pseudotime, conos object and the start/end region pseudotime ranges (reg1,reg2 are tuples of time values)
lin.dev.pt <- function(pt,con,reg1,reg2) {
  # determine cells associated with the two regions
  cel1 <- names(pt)[pt>=reg1[1] & pt<=reg1[2]]
  cel2 <- names(pt)[pt>=reg2[1] & pt<=reg2[2]]
  cel3 <- names(pt)[pt>reg1[2] & pt<reg2[1]]
  
  # construct a joint count matrix
  mat <- conos:::rawMatricesWithCommonGenes(con)
  mat <- do.call(rbind,lapply(mat,function(x) x[rownames(x) %in% c(cel1,cel2,cel3),,drop=F]))
  lin.dev(cel1,cel2,cel3,mat)
}
rss <- lin.dev.pt(ptr,ncdcon,c(0,1.2e3),c(2.6e3,6e3))
rss2 <- lin.dev.pt(ptr,ncdcon,c(0,1.2e3),c(1.5e3,2.0e3))
rss3 <- lin.dev.pt(ptr,ncdcon,c(1.5e3,2.0e3),c(3e3,6e3))
pdf(file='mesenchymal.bridge.residuals.pdf',width=5,height=5); print(p1); dev.off();
null device 
          1 

sim2: PODXL … NPNT, TNNT2 NPHS1, NDNF, MME, GRN? MYC, ZFAND5, MAFF show nice foci close to the bridge on the fibroblast side

ncdcon$plotGraph(alpha=0.3,size=0.3,gene='PHOX2B',plot.na=F,gradient.range.quantile=0.99)

Run GSEA

source("~/keith/me3/gosim.r")
load("~/keith/me3/org.Hs.GOenvs.RData")
pc <- rss
gsl <- ls(env=org.Hs.GO2Symbol)
gsl.ng <- unlist(lapply(sn(gsl),function(go) sum(get(go,env=org.Hs.GO2Symbol) %in% names(pc))))
gsl <- gsl[gsl.ng>=10 & gsl.ng<=2e3]
val <- rss
gsea.rss.p1 <- iterative.bulk.gsea(values=val,set.list=lapply(sn(gsl),get,env=org.Hs.GO2Symbol),power=1,mc.cores=32)

using simple PPT

require(crestree)

Start with a high-dimensional embedding

old.emb <- ncdcon$embedding;
hiemb <- ncdcon$misc$embeddings$linfit <- con$embedGraph(method='largeVis',target.dims=4)
ncdcon$embedding <- old.emb;

restrict to the cells of interest

hiemb <- hiemb[rownames(hiemb)%in% cells,]
emb <- ncdcon$embedding[rownames(ncdcon$embedding) %in% cells,]
ptree <- ppt.tree(X=t(hiemb),emb=NA,lambda=100,sigma=0.5,metrics='euclidean',M=300,plot=F,output = F)
plotppt(ptree,emb,cex.tree=0.1,cex.main=0.2,lwd.tree=1,tips=T)
ptree <- setroot(ptree,259)

ERBB3 and NRG1

gns <- c("ERBB3","NRG1"); 
p0 <- ncdcon$plotGraph(alpha=0.1,size=0.1,groups=annot,palette=annot.pal,plot.na=F,raster=T,raster.width=4,raster.height=4)
pl <- lapply(gns,function(g) ncdcon$plotGraph(alpha=0.2,size=0.1,gene=g,plot.na=F,gradient.range.quantile=0.99,raster=T,raster.width=4,raster.height=4) +geom_label(x=Inf,y=Inf,vjust=1,hjust=1,label=g,data=data.frame(x=c(1)),label.size=0))
pp <- plot_grid(plotlist=c(list(p0),pl),nrow=1)
pdf("erbb3_nrg1.pdf",width=9,height=3); print(pp); dev.off();
png 
  2 
pp

Mesenchymal

Aspect ration difference between the selected subset and the actual one

Reduce(eval('/'),apply(ncdcon$embedding,2,function(x) diff(range(x))))/Reduce(eval('/'),apply(ncdcon$embedding[mes.cells,],2,function(x) diff(range(x))))
[1] 0.5236925
p0 <- ncdcon$plotGraph(alpha=0.2,size=0.2,groups=annot[mes.cells],palette=annot.pal,mark.groups=F,plot.na=F,gradient.range.quantile=0.95,raster=T,raster.width=2,raster.height=1) +
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0)) + 
  #geom_label(x=-Inf,y=Inf,vjust=1,hjust=0,label='clusters',data=data.frame(x=c(1)),label.size=0) +
  theme(panel.grid.major = element_blank())
p0

ncdcon$plotGraph(alpha=0.5,size=0.5,colors=conos:::getGeneExpression(ncdcon,"PODXL")[mes.cells],mark.groups=F,plot.na=F,gradient.range.quantile=0.99,raster=T,raster.width=1.5,raster.height=1.5)+
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0)) + 
  #geom_label(x=-Inf,y=Inf,vjust=1,hjust=0,label='clusters',data=data.frame(x=c(1)),label.size=0) +
  theme(panel.grid.major = element_blank())

mult <- 1.5; pdf(file='mes.expr.pdf',width=1*mult,height=length(pl)*0.58*mult); print(pp); dev.off()
null device 
          1 
LS0tCnRpdGxlOiAiRmlndXJlIDEgcGxvdHMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCkxvYWQgbGlicmFyaWVzCgpgYGB7ciBlY2hvPUZBTFNFfQpsaWJyYXJ5KHBhZ29kYTIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoY29ub3MpCmxpYnJhcnkocGFyYWxsZWwpCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeShnZ3JlcGVsKQpsaWJyYXJ5KE1hdHJpeCkKI3NvdXJjZSgiL2hvbWUvcGtoYXJjaGVua28vbS9wYXZhbi9ETEkvY29ucDIuciIpCmBgYAoKCgpDdXN0b20gY2x1c3RlcmluZwpgYGB7cn0KI25jZGNvbiRmaW5kQ29tbXVuaXRpZXMobWV0aG9kPXJsZWlkZW4uY29tbXVuaXR5LHI9YygxLDAuNSkpCiNuY2Rjb24kZmluZENvbW11bml0aWVzKG1ldGhvZD1sZWlkZW4uY29tbXVuaXR5LHI9MS4yKQpgYGAKCgoKTG9hZCBOQi1vbmx5IGFsaWdubWVudApgYGB7cn0KbmNkY29uIDwtIHJlYWRSRFMoIn5wa2hhcmNoZW5rby9tL25pbmliL05CL25jZGNvbi5yZHMiKQpuY2Rjb24gPC0gQ29ub3MkbmV3KG5jZGNvbikKYGBgCgpgYGB7cn0KZG91YmxldFNjb3JlcyA8LSB1bmxpc3QocmVhZFJEUygifnBraGFyY2hlbmtvL20vbmluaWIvTkIvZG91YmxldFNjb3Jlcy5yZHMiKSkKYGBgCgoKSW5pdGlhbCBhbm5vdGF0aW9uCmBgYHtyfQpuaW5pYl9iYW5ub3QgPC0gcmVhZFJEUygifnBraGFyY2hlbmtvL20vbmluaWIvTkIvbmluaWJfYmFubm90LnJkcyIpCgpgYGAKCkNsZWFuIHVwIGFubm90YXRpb24KYGBge3J9CmFubm90IDwtIHJlYWRSRFMoIi9kMC1tZW5kZWwvaG9tZS9tZWlzbC9Xb3JrcGxhY2UvbmV1cm9ibGFzdG9tYS9jZWxsLmFubm90YXRpb24uSmFuMjAyMC5yZHMiKSRjZWxsYW5vCmFubm90IDwtIHNldE5hbWVzKGFzLmNoYXJhY3Rlcihhbm5vdCksbmFtZXMoYW5ub3QpKQphbm5vdFthbm5vdD09J3Vua25vd24nXSA8LSAnVGgnICMgQ2EyKyBtZWRpYXRlZCBhcG9wdG9zaXMsIGludm9saXZuZyBMQ0ssIENEM0QsIFJBQzIsIGV0Yy4gTHVtcGluZyBpbiB3aXRoIFRoIAphbm5vdFthbm5vdD09J1BsYXNtYWN5dG9pZCddIDwtICdwREMnCmFubm90W2dyZXAoJ0N5dG90b3hpYycsYW5ub3QpXSA8LSAnVGN5dG8nCmFubm90W2dyZXAoJ1QgaGVscGVyJyxhbm5vdCldIDwtICdUaCcKYW5ub3RbZ3JlcCgnTksnLGFubm90KV0gPC0gJ05LJwphbm5vdFtncmVwKCdCY2VsbCcsYW5ub3QpXSA8LSAnQicKYW5ub3RbZ3JlcCgnUGxhc21hQ2VsbCcsYW5ub3QpXSA8LSAnUGxhc21hJwphbm5vdFtncmVwKCdNYXN0Jyxhbm5vdCldIDwtICdNYXN0Jwphbm5vdFtncmVwKCJCcmlkZ2UiLGFubm90KV0gPC0gJ1NDUC1saWtlJwpkYW5ub3QgPC0gYXMuZmFjdG9yKGFubm90KQoKYW5ub3RbZ3JlcCgiU09YMTF8U3RyZXNzfGV1cm9uYWx8UHJvbGlmIixhbm5vdCldIDwtICdOb3JhZHJlbmVyZ2ljJwphbm5vdFtncmVwKCJCcmlkZ2UiLGFubm90KV0gPC0gJ1NDUC1saWtlJwphbm5vdCA8LSBhcy5mYWN0b3IoYW5ub3QpOwojIHJlbmFtaW5ncwpsZXZlbHMoYW5ub3QpIDwtIGdzdWIoIk5vcmFkcmVuZXJnaWMiLCJBZHJlbmVyZ2ljIixsZXZlbHMoYW5ub3QpKQpgYGAKCnNldCB1cCBwYWxsZXRzCmBgYHtyfQpzZXQuc2VlZCgzKQphbm5vdC5wYWwgPC0gc2V0TmFtZXMoc2FtcGxlKHJhaW5ib3cobGVuZ3RoKGxldmVscyhhbm5vdCkpLHY9MC45NSxzPTAuNykpLGxldmVscyhhbm5vdCkpOwphbm5vdC5wYWxmIDwtIGZ1bmN0aW9uKG4pIHJldHVybihhbm5vdC5wYWwpCmRhbm5vdC5wYWwgPC0gc2V0TmFtZXMoc2FtcGxlKHJhaW5ib3cobGVuZ3RoKGxldmVscyhkYW5ub3QpKSx2PTAuODUscz0wLjkpKSxsZXZlbHMoZGFubm90KSk7CnggPC0gd2hpY2gobGV2ZWxzKGRhbm5vdCkgJWluJSBsZXZlbHMoYW5ub3QpKTsgZGFubm90LnBhbFt4XSA8LSBhbm5vdC5wYWxbbWF0Y2gobGV2ZWxzKGRhbm5vdClbeF0sbGV2ZWxzKGFubm90KSldCmRhbm5vdC5wYWxmIDwtIGZ1bmN0aW9uKG4pIHJldHVybihkYW5ub3QucGFsKQoKIyBmb3Igc2FtcGxlcwpzYW1wbGUucGFsIDwtIHNldE5hbWVzKHNhbXBsZShyYWluYm93KGxlbmd0aChuY2Rjb24kc2FtcGxlcyksdj0wLjcscz0wLjkpKSxuYW1lcyhuY2Rjb24kc2FtcGxlcykpCnNhbXBsZS5wYWwgPC0gc2FtcGxlLnBhbFtjKHNvcnQobmFtZXMoc2FtcGxlLnBhbCkpWy0xXSwnQWRyJyldCnNhbXBsZS5wYWxmIDwtIGZ1bmN0aW9uKG4pIHJldHVybihzYW1wbGUucGFsWzE6bl0pCnNhbXBsZS5wYWxmIDwtIGZ1bmN0aW9uKG4pIHsgYnJvd3NlcigpOyByZXR1cm4oc2FtcGxlLnBhbFsxOm5dKX0KYGBgCgoKYGBge3IgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9NX0KcDEgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LGdyb3Vwcz1uY2Rjb24kZ2V0RGF0YXNldFBlckNlbGwoKSxwbG90Lm5hPUYsbWFyay5ncm91cHM9RixwYWxldHRlPXNhbXBsZS5wYWwpCnAyIDwtIG5jZGNvbiRwbG90R3JhcGgoYWxwaGE9MC4wMyxzaXplPTAuNSxncm91cHM9YW5ub3QscGxvdC5uYT1GLG1hcmsuZ3JvdXBzPVQscGFsZXR0ZT1hbm5vdC5wYWxmKQojcDMgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LHBsb3QubmE9RixtYXJrLmdyb3Vwcz1ULGNsdXN0ZXJpbmc9J2xlaWRlbicpCnAzIDwtIG5jZGNvbiRwbG90R3JhcGgoYWxwaGE9MC4wMyxzaXplPTAuNSxncm91cHM9ZGFubm90LHBsb3QubmE9RixtYXJrLmdyb3Vwcz1ULHBhbGV0dGU9ZGFubm90LnBhbGYpCnBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHAxLHAyLHAzKSxucm93PTEpCmBgYAoKCndyaXRlIG91dCBwMiBhcHAKYGBge3J9CnNvdXJjZSgifi9tL3BhdmFuL0RMSS9jb25wMi5yIikKY2VsbC5zdWJzZXQgPC0gdW5pcXVlKGMoc2FtcGxlKHJvd25hbWVzKG5jZGNvbiRlbWJlZGRpbmcpLDJlMyksbmFtZXMoYW5ub3QpW2Fubm90PT0nU0NQLWxpa2UnXSkpCm5jZGNvbi5wMiA8LSBwMmFwcDRjb25vcyhuY2Rjb24sZmlsZT0nbmNkY29uLmJpbicsbWV0YWRhdGEgPSBsaXN0KGNvYXJzZT1hbm5vdCxkZXRhaWxlZD1kYW5ub3QsY252PWNudi5zYyksY2VsbC5zdWJzZXQgPSBjZWxsLnN1YnNldCkKYGBgCgoKU2FtcGxlIHBhbmVsOgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0yfQpwcCA8LSBuY2Rjb24kcGxvdFBhbmVsKGdyb3Vwcz1hbm5vdCxwbG90Lm5hPUYsYWxwaGE9MC4xLHNpemU9MC4yLHVzZS5jb21tb24uZW1iZWRkaW5nID0gVCwgbmNvbD0yLCBtYXJrLmdyb3Vwcz1GLHBhbGV0dGU9YW5ub3QucGFsLHJhc3Rlcj1ULCByYXN0ZXIud2lkdGg9MSxyYXN0ZXIuaGVpZ2h0PTEsdGl0bGUuc2l6ZT00KQojcGRmKCdwYW5lbC5wZGYnLGhlaWdodD04LHdpZHRoPTIpOyBwcmludChwcCk7IGRldi5vZmYoKTsKcHAKYGBgCgoKYGBge3IgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9Nn0KbmNkY29uJHBsb3RQYW5lbChnZW5lPSdUQlgxOCcscGxvdC5uYT1GLGFscGhhPTAuOSxzaXplPTAuMix1c2UuY29tbW9uLmVtYmVkZGluZyA9IFQpCmBgYAoKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KbmNkY29uJHBsb3RHcmFwaChnZW5lPSdUT1AyQScsYWxwaGE9MC41LHNpemU9MC4xKQpgYGAKCmBgYHtyfQpzb3VyY2UoIn4vbS9wYXZhbi9ETEkvY29ucDIuciIpCm5jZGNvbi5wMiA8LSBwMmFwcDRjb25vcyhuY2Rjb24sZmlsZT0nbmNkY29uLmJpbicsbWF4LmNlbGxzPTFlNCxtZXRhZGF0YSA9IGxpc3QoYW5ub3Q9YXMuZmFjdG9yKGFubm90KSxkYW5ub3Q9YXMuZmFjdG9yKGRhbm5vdCkpKQpgYGAKCgpNYXJrZXIgZ2VuZXM6CmBgYHtyfQpuZmFjIDwtIGFubm90OwpgYGAKCmBgYHtyfQpuZmFjLmRlIDwtIG5jZGNvbiRnZXREaWZmZXJlbnRpYWxHZW5lcyhncm91cHM9bmZhYyxuLmNvcmVzPTMwLGFwcGVuZC5hdWM9VFJVRSx6LnRocmVzaG9sZD0wLHVwcmVndWxhdGVkLm9ubHk9VCkKYGBgCgoKYGBge3IgZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9MTJ9CnNvdXJjZSgifi9tL3AyL2Nvbm9zL1IvcGxvdC5SIikKcHAgPC0gcGxvdERFaGVhdG1hcChuY2Rjb24sbmZhYyxuZmFjLmRlLG4uZ2VuZXMucGVyLmNsdXN0ZXIgPSAyMCAsc2hvdy5nZW5lLmNsdXN0ZXJzPVQsY29sdW1uLm1ldGFkYXRhPWxpc3Qoc2FtcGxlcz1uY2Rjb24kZ2V0RGF0YXNldFBlckNlbGwoKSksb3JkZXIuY2x1c3RlcnMgPSBULHVzZV9yYXN0ZXI9RiwgY29sdW1uLm1ldGFkYXRhLmNvbG9ycyA9IGxpc3QoY2x1c3RlcnM9YW5ub3QucGFsLHNhbXBsZXM9c2FtcGxlLnBhbCkpCnBkZihmaWxlPSduZmFjLmhlYXRtYXAyLnBkZicsd2lkdGg9MTAsaGVpZ2h0PTMwKTsgcHJpbnQocHApOyBkZXYub2ZmKCk7CmBgYAoKQSBzbWFsbCB2ZXJzaW9uIG9mIHRoZSBoZXRtYXAsIGZvciB0aGUgbWFpbiBmaWd1cmUKYGBge3IgZmlnLndpZHRoPTYsZmlnLmhlaWdodD02fQpzb3VyY2UoIn4vbS9wMi9jb25vcy9SL3Bsb3QuUiIpCmdlbmVzIDwtIGMoIkNENzlCIiwnTVM0QTEnLCdQTFZBUCcsJ0VQQ0FNJywnU1BJTksyJywnSUw3UicsJ0xZWicsJ0MxUUInLCdDTEVDMTBBJywnQ09MMUExJywnTFVNJywnUzEwMEE5JywnSUwxQicsJ01ZTDknLCdHTkxZJywnUlROMScsJ01ESycsJ05OQVQnLCdJUkY0JywnTVlIMTEnLCdGQ1JMNScsJ1BMUDEnLCdHWk1BJywnUk9SQScsJ1RJR0lUJykKcHAgPC0gcGxvdERFaGVhdG1hcChuY2Rjb24sbmZhYyxuZmFjLmRlLG4uZ2VuZXMucGVyLmNsdXN0ZXIgPSAyMCAsc2hvdy5nZW5lLmNsdXN0ZXJzPVQsY29sdW1uLm1ldGFkYXRhPWxpc3Qoc2FtcGxlcz1uY2Rjb24kZ2V0RGF0YXNldFBlckNlbGwoKSksIGNvbHVtbi5tZXRhZGF0YS5jb2xvcnMgPSBsaXN0KGNsdXN0ZXJzPWFubm90LnBhbCxzYW1wbGVzPXNhbXBsZS5wYWwpLCBvcmRlci5jbHVzdGVycyA9IFQsIGFkZGl0aW9uYWwuZ2VuZXMgPSBnZW5lcywgbGFiZWxlZC5nZW5lLnN1YnNldCA9IGdlbmVzLCB1c2VfcmFzdGVyPUYsbWluLmF1YyA9IDAuNzkpCnBwCmBgYAoKYGBge3J9CnBkZihmaWxlPSdtYXJrZXJzLnNtYWxsLnBkZicsd2lkdGg9NixoZWlnaHQ9Nik7IHByaW50KHBwKTsgZGV2Lm9mZigpOwpgYGAKCk1ha2UgYSB2ZXJ5IHNpbXBsaWZpZWQgdmVyc2lvbgpgYGB7cn0Kc2Fubm90IDwtIHNldE5hbWVzKGFzLmNoYXJhY3Rlcihhbm5vdCksbmFtZXMoYW5ub3QpKTsKc2Fubm90W2dyZXAoIl5UfElMQzN8TksiLHNhbm5vdCldIDwtICdUIGNlbGxzJwpzYW5ub3RbZ3JlcCgiXkIkfFBsYXNtYXxwREMiLHNhbm5vdCldIDwtICdCIGNlbGxzJwpzYW5ub3RbZ3JlcCgiTWFjcm9wfE1vbm98bURDfE1hc3QiLHNhbm5vdCldIDwtICdNeWVsb2lkJwpzYW5ub3QgPC0gYXMuZmFjdG9yKHNhbm5vdCkKCnNhbm5vdC5wYWwgPC0gc2V0TmFtZXMoYW5ub3QucGFsW21hdGNoKGxldmVscyhzYW5ub3QpLG5hbWVzKGFubm90LnBhbCkpXSxsZXZlbHMoc2Fubm90KSkKc2Fubm90LnBhbFsnVCBjZWxscyddIDwtIGFubm90LnBhbFsnVGN5dG8nXQpzYW5ub3QucGFsWydCIGNlbGxzJ10gPC0gYW5ub3QucGFsWydCJ10Kc2Fubm90LnBhbFsnTXllbG9pZCddIDwtIGFubm90LnBhbFsnTWFjcm9waGFnZXMnXQpgYGAKCmBgYHtyfQpzYW5ub3QuZGUgPC0gbmNkY29uJGdldERpZmZlcmVudGlhbEdlbmVzKGdyb3Vwcz1zYW5ub3Qsbi5jb3Jlcz0zMCxhcHBlbmQuYXVjPVRSVUUsei50aHJlc2hvbGQ9MCx1cHJlZ3VsYXRlZC5vbmx5PVQpCmBgYAoKYGBge3J9CnNhbm5vdC5kZVtbJ0FkcmVuZXJnaWMnXV0gJT4lIGFycmFuZ2UoLUFVQykgJT4lIGhlYWQoMzApCmBgYAoKCmBgYHtyIGZpZy53aWR0aD0xMixmaWcuaGVpZ2h0PTEyfQpzb3VyY2UoIn4vbS9wMi9jb25vcy9SL3Bsb3QuUiIpCnBwIDwtIHBsb3RERWhlYXRtYXAobmNkY29uLHNhbm5vdCxzYW5ub3QuZGUsbi5nZW5lcy5wZXIuY2x1c3RlciA9IDIwICxzaG93LmdlbmUuY2x1c3RlcnM9VCxjb2x1bW4ubWV0YWRhdGE9bGlzdChzYW1wbGVzPW5jZGNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpKSxvcmRlci5jbHVzdGVycyA9IFQsIGNvbHVtbi5tZXRhZGF0YS5jb2xvcnMgPSBsaXN0KGNsdXN0ZXJzPXNhbm5vdC5wYWwsc2FtcGxlcz1zYW1wbGUpKQpwZGYoZmlsZT0nc2Fubm90LmhlYXRtYXAucGRmJyx3aWR0aD0xMCxoZWlnaHQ9MzApOyBwcmludChwcCk7IGRldi5vZmYoKTsKYGBgCgpBIHNtYWxsIHZlcnNpb24gb2YgdGhlIGhldG1hcCwgZm9yIHRoZSBtYWluIGZpZ3VyZQpgYGB7ciBmaWcud2lkdGg9NCxmaWcuaGVpZ2h0PTZ9CnNvdXJjZSgifi9tL3AyL2Nvbm9zL1IvcGxvdC5SIikKZ2VuZXMgPC0gYygiQ0Q3OUIiLCdNUzRBMScsJ1BMVkFQJywnRVBDQU0nLCdTUElOSzInLCdJTDdSJywnTFlaJywnQzFRQicsJ0NMRUMxMEEnLCdDT0wxQTEnLCdMVU0nLCdTMTAwQTknLCdJTDFCJywnTVlMOScsJ0dOTFknLCdSVE4xJywnTURLJywnTk5BVCcsJ0lSRjQnLCdNWUgxMScsJ0ZDUkw1JywnUExQMScsJ0daTUEnLCdST1JBJywnVElHSVQnKQpnZW5lcyA8LSBjKCJDRDNEIiwnQ0Q3JywnQ0Q3OUEnLCdITEEtRFJBJywnQ0Q3NCcsJ0xZWicsJ1NUTU4yJywnTURLJywnTVlMOScsJ01ZSDExJywnUEVDQU0xJywnTFVNJywnUExQMScpCnRmIDwtIHNhbm5vdFt1bmxpc3QodGFwcGx5KDE6bGVuZ3RoKHNhbm5vdCksc2Fubm90LGZ1bmN0aW9uKHgpIHsgaWYobGVuZ3RoKHgpPjJlMikgeCA8LSBzYW1wbGUoeCwyZTIpOyB4fSkpXQpwcCA8LSBwbG90REVoZWF0bWFwKG5jZGNvbix0ZixzYW5ub3QuZGUsbi5nZW5lcy5wZXIuY2x1c3RlciA9IDIwICxzaG93LmdlbmUuY2x1c3RlcnM9VCxjb2x1bW4ubWV0YWRhdGE9bGlzdChzYW1wbGVzPW5jZGNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpKSwgY29sdW1uLm1ldGFkYXRhLmNvbG9ycyA9IGxpc3QoY2x1c3RlcnM9c2Fubm90LnBhbCxzYW1wbGVzPXNhbXBsZS5wYWwpLCBvcmRlci5jbHVzdGVycyA9IFQsIGFkZGl0aW9uYWwuZ2VuZXMgPSBnZW5lcywgbGFiZWxlZC5nZW5lLnN1YnNldCA9IGdlbmVzLCBtaW4uYXVjID0gMC43NSx1c2VfcmFzdGVyID0gVCxyYXN0ZXJfZGV2aWNlID0gIkNhaXJvUE5HIixhdmVyYWdpbmcud2luZG93PTUpCnBwCmBgYAoKCmBgYHtyfQpwZGYoZmlsZT0nc2Fubm90LnNlbGVjdC5wZGYnLHdpZHRoPTQsaGVpZ2h0PTYpOyBwcmludChwcCk7IGRldi5vZmYoKTsKYGBgCgpgYGB7cn0Kc291cmNlKCJ+L20vcDIvY29ub3MvUi9wbG90LlIiKQpwZGYoZmlsZT0nc2Fubm90LnNlbGVjdC5wZGYnLHdpZHRoPTUsaGVpZ2h0PTYpOyAKcGxvdERFaGVhdG1hcChuY2Rjb24sc2Fubm90LHNhbm5vdC5kZSxuLmdlbmVzLnBlci5jbHVzdGVyID0gMjAgLHNob3cuZ2VuZS5jbHVzdGVycz1ULGNvbHVtbi5tZXRhZGF0YT1saXN0KHNhbXBsZXM9bmNkY29uJGdldERhdGFzZXRQZXJDZWxsKCkpLCBjb2x1bW4ubWV0YWRhdGEuY29sb3JzID0gbGlzdChjbHVzdGVycz1zYW5ub3QucGFsLHNhbXBsZXM9c2FtcGxlLnBhbCksIG9yZGVyLmNsdXN0ZXJzID0gVCwgYWRkaXRpb25hbC5nZW5lcyA9IGdlbmVzLCBsYWJlbGVkLmdlbmUuc3Vic2V0ID0gZ2VuZXMsIG1pbi5hdWMgPSAwLjc1KQpkZXYub2ZmKCk7CmBgYAoKCmBgYHtyIGZpZy53aWR0aD01LGZpZy5oZWlnaHQ9Nn0Kc291cmNlKCJ+L20vcDIvY29ub3MvUi9wbG90LlIiKQpnZW5lcyA8LSBjKCJDRDc5QiIsJ01TNEExJywnUExWQVAnLCdFUENBTScsJ1NQSU5LMicsJ0lMN1InLCdMWVonLCdDMVFCJywnQ0xFQzEwQScsJ0NPTDFBMScsJ0xVTScsJ1MxMDBBOScsJ0lMMUInLCdNWUw5JywnR05MWScsJ1JUTjEnLCdNREsnLCdOTkFUJywnSVJGNCcsJ01ZSDExJywnRkNSTDUnLCdQTFAxJywnR1pNQScsJ1JPUkEnLCdUSUdJVCcpCnBwIDwtIHBsb3RERWhlYXRtYXAobmNkY29uLHNhbm5vdCxzYW5ub3QuZGUsbi5nZW5lcy5wZXIuY2x1c3RlciA9IDIwICxzaG93LmdlbmUuY2x1c3RlcnM9VCxjb2x1bW4ubWV0YWRhdGE9bGlzdChzYW1wbGVzPW5jZGNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpKSwgY29sdW1uLm1ldGFkYXRhLmNvbG9ycyA9IGxpc3QoY2x1c3RlcnM9c2Fubm90LnBhbCxzYW1wbGVzPXNhbXBsZS5wYWwpLCBvcmRlci5jbHVzdGVycyA9IFQsIG1pbi5hdWMgPSAwLjc1LCByb3cubGFiZWwuZm9udC5zaXplID0gMykKcHAKYGBgCgpgYGB7cn0KcGRmKGZpbGU9J3Nhbm5vdC5zbWFsbC5wZGYnLHdpZHRoPTYsaGVpZ2h0PTYpOyBwcmludChwcCk7IGRldi5vZmYoKTsKYGBgCgoKRHJhdyBqdXN0IHRoZSB0dW1vciBjZWxsIGFubm90YXRpb25zOgpgYGB7ciBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD02fQpzb3VyY2UoIn4vbS9wMi9jb25vcy9SL3Bsb3QuUiIpCnRmIDwtIGRyb3BsZXZlbHMoc2Fubm90W2dyZXAoIkFkcnxTQ1B8TWVzZW5jaCIsc2Fubm90KV0pOyB0ZiA8LWZhY3Rvcih0ZixsZXZlbHM9bGV2ZWxzKHRmKVtjKDIsMywxKV0pCnBwIDwtIHBsb3RERWhlYXRtYXAobmNkY29uLHRmLHNhbm5vdC5kZSxuLmdlbmVzLnBlci5jbHVzdGVyID0gMTAsIGV4cHJlc3Npb24ucXVhbnRpbGU9MC45NSwgc2hvdy5nZW5lLmNsdXN0ZXJzPVQsY29sdW1uLm1ldGFkYXRhPWxpc3Qoc2FtcGxlcz1uY2Rjb24kZ2V0RGF0YXNldFBlckNlbGwoKSksb3JkZXIuY2x1c3RlcnMgPSBGLCBjb2x1bW4ubWV0YWRhdGEuY29sb3JzID0gbGlzdChjbHVzdGVycz1zYW5ub3QucGFsLHNhbXBsZXM9c2FtcGxlLnBhbCksYWRkaXRpb25hbC5nZW5lcyA9IGMoIlNPWDEwIiwiUFJSWDEiLCAiTEVQUiIsICJQREdGUkEiLCJUSCIsIklTTDEiLCJOUkcxIiwiRVJCQjMiKSxleGNsdWRlLmdlbmVzPWMoIkNSWUFCIiksbWluLmF1Yz0wLjc1LHJvdy5sYWJlbC5mb250LnNpemUgPSAxMCxzcGxpdD1ULCBzcGxpdC5nYXA9MSxjb2x1bW5fdGl0bGUgPSBOVUxMLHVzZV9yYXN0ZXIgPSBULHJhc3Rlcl9kZXZpY2UgPSAiQ2Fpcm9QTkciLGF2ZXJhZ2luZy53aW5kb3c9NSkKcHAKYGBgCgpgYGB7cn0KcGRmKGZpbGU9J05CLm1hcmtlcnMuc21hbGwucGRmJyx3aWR0aD00LGhlaWdodD02KTsgcHJpbnQocHApOyBkZXYub2ZmKCk7CmBgYAoKYGBge3IgZmlnLndpZHRoPTUsZmlnLmhlaWdodD01fQpuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuMyxzaXplPTAuNSxwbG90Lm5hPUYsbWFyay5ncm91cHM9VCxnZW5lPSdST1JBJykKYGBgCgojIyMgUGxvdCBDTlYgc2NvcmVzCgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0zMH0KbmNkY29uJHBsb3RQYW5lbChnZW5lPSdQTFAxJyxhbHBoYT0xLHNpemU9MC4zLCB1c2UuY29tbW9uLmVtYmVkZGluZyA9IFQsbmNvbD0yLGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOCkKYGBgCgpTZWUgaW50ZXJhY3Rpb24gYmV0d2VlbiBQTFAxIGV4cHJlc3Npb24gaW4gdGhlICdicmlkZ2UnIGNlbGwgcG9wdWxhdGlvbiBhbmQgdGhlIENOViBzY29yZXMgYWNyb3NzIGRpZmZlcmVudCBzYW1wbGVzCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMH0KCiNleHAgPC0gcG1pbihjb25vczo6OmdldEdlbmVFeHByZXNzaW9uKG5jZGNvbiwnUExQMScpLGNvbm9zOjo6Z2V0R2VuZUV4cHJlc3Npb24obmNkY29uLCdTMTAwQicpKQoKZXhwIDwtIGNvbm9zOjo6Z2V0R2VuZUV4cHJlc3Npb24obmNkY29uLCdQTFAxJykKc2FtcGxlZiA8LSBuY2Rjb24kZ2V0RGF0YXNldFBlckNlbGwoKQoKcGwgPC0gbGFwcGx5KG5hbWVzKGN1dG9mZi5saXN0KSxmdW5jdGlvbihzYW4pIHsKICBjbiA8LSBpbnRlcnNlY3QobmFtZXMoYW5ub3QpW2Fubm90PT0nU0NQLWxpa2UnXSxuYW1lcyhzYW1wbGVmKVtzYW1wbGVmPT1zYW5dKQogIGRmIDwtIGRhdGEuZnJhbWUoY252PWNudi5zY1ttYXRjaChjbixuYW1lcyhjbnYuc2MpKV0sZXhwPWV4cFttYXRjaChjbixuYW1lcyhleHApKV0scm93Lm5hbWVzPWNuKQogICNnZ3Bsb3QoZGYsYWVzKGV4cCxjbnYpKSArIGdlb21fcG9pbnQoY29sPWFkanVzdGNvbG9yKDEsYWxwaGE9MC4zKSkgKyBnZ3RpdGxlKHNhbikgK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlPSJkYXNoZWQiLCBjb2xvciA9ICJyZWQiKQogICNnZ3Bsb3QoZGYsYWVzKGV4cCxjbnYpKSArIGdlb21fcG9pbnQoY29sPWFkanVzdGNvbG9yKHBhZ29kYTI6Ojp2YWwyY29sKGRvdWJsZXRTY29yZXNbY25dLHpsaW09YygwLDAuNSksZ3JhZGllbnRQYWxldHRlPWNvbG9yUmFtcFBhbGV0dGUoYygnYmxhY2snLCdyZWQnKSkoMTAyNCkpLGFscGhhPTAuMykpICsgZ2d0aXRsZShzYW4pICtnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3IgPSAicmVkIikKICBnZ3Bsb3QoZGYsYWVzKGV4cCxjbnYpKSArIGdlb21fcG9pbnQoY29sPWFkanVzdGNvbG9yKGlmZWxzZShkb3VibGV0U2NvcmVzW21hdGNoKGNuLG5hbWVzKGRvdWJsZXRTY29yZXMpKV0+MC4yLCdyZWQnLCdibGFjaycpLGFscGhhPTAuMykpICsgZ2d0aXRsZShzYW4pICtnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3IgPSAicmVkIikKfSkKCnBsb3RfZ3JpZChwbG90bGlzdD1wbCkKYGBgCgoKSnVzdCB0aGUgc2NvcmVzCmBgYHtyfQpjbnYuc2MgPC0gcmVhZFJEUygiL2QwLW1lbmRlbC9ob21lL21laXNsL1dvcmtwbGFjZS9uZXVyb2JsYXN0b21hL0FtcFNjb3JlLnJkcyIpCmBgYAoKU3RyaW5nZW50IHNjb3JlczoKYGBge3J9CmxvYWQoIi9kMC1tZW5kZWwvaG9tZS9tZWlzbC9Xb3JrcGxhY2UvbmV1cm9ibGFzdG9tYS9GaWd1cmVzL1MxL0NOVi9GRFI1LlJEYXRhIikKbmFtZXMoY3V0b2ZmLmxpc3QpIDwtIGdzdWIoIlxcLi4qIiwiIixuYW1lcyhjdXRvZmYubGlzdCkpCmN1dG9mZi5saXN0IDwtIHNldE5hbWVzKHVubGlzdChjdXRvZmYubGlzdCksbmFtZXMoY3V0b2ZmLmxpc3QpKQoKdmFsaWQuc2FtcGxlcyA8LSBjKCdOQjEyJywnTkIyNicpCiN2YWxpZC5zYW1wbGVzIDwtIHVuaXF1ZShnc3ViKCJfLioiLCIiLG5hbWVzKGNudi5zYykpKTsKY252LnNjIDwtIGNudi5zY1tuY2Rjb24kZ2V0RGF0YXNldFBlckNlbGwoKVtuYW1lcyhjbnYuc2MpXSAlaW4lIHZhbGlkLnNhbXBsZXNdCmBgYAoKRGV0YWlsZWQgaW5mbwphbGxTY29yZUE6IHNjYWxlZCBnZW5lIHNldCBhdmVyYWdlIGV4cHJlc3Npb24uIApjdXRvZmYubGlzdDogY3V0b2ZmIGZvciBlYWNoIHBhdGllbnQKQ05WLmNlbGxzOiAgYW1wbGlmaWVkIGNlbGwgbmFtZSAKCmBgYHtyfQpsb2FkKCIvZDAtbWVuZGVsL2hvbWUvbWVpc2wvV29ya3BsYWNlL25ldXJvYmxhc3RvbWEvRmlndXJlcy9DTlYvQ05WLjAyMTEuUkRhdGEiKQpuYW1lcyhjdXRvZmYubGlzdCkgPC0gZ3N1YigiXFwuLioiLCIiLG5hbWVzKGN1dG9mZi5saXN0KSkKY3V0b2ZmLmxpc3QgPC0gc2V0TmFtZXModW5saXN0KGN1dG9mZi5saXN0KSxuYW1lcyhjdXRvZmYubGlzdCkpCmBgYAoKQ2FsY3VsYXRlIGEgc2NhbGVkIHNjb3JlCmBgYHtyfQoKY2VsbC5jbnYudGhyZXNob2xkcyA8LSBzZXROYW1lcyhjdXRvZmYubGlzdFtnc3ViKCJfLioiLCIiLG5hbWVzKGFsbFNjb3JlQSkpXSxuYW1lcyhhbGxTY29yZUEpKQpjbnYuc2MgPC0gYWxsU2NvcmVBLWNlbGwuY252LnRocmVzaG9sZHMKYGBgCgpMaW1pdCB0byB0aGUgc2FtcGxlcyB3aGVyZSB3ZSBzZWUgdHVtb3IgY2VsbHMgYW5kIENOVnMKYGBge3J9CnZhbGlkLnNhbXBsZXMgPC0gYygnTkIwMScsJ05CMDknLCdOQjEyJywnTkIxMycsJ05CMTUnLCdOQjE4JywnTkIxOScsJ05CMjAnLCdOQjIxJywnTkIyNCcsJ05CMjYnKQpjbnYuc2MgPC0gY252LnNjW25jZGNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpW25hbWVzKGNudi5zYyldICVpbiUgdmFsaWQuc2FtcGxlc10KYGBgCgoKCgoKYGBge3J9Cmhpc3QoY252LnNjLGNvbD0nd2hlYXQnKQpgYGAKCmBgYHtyIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTR9CmVtYiA8LW5jZGNvbiRlbWJlZGRpbmcKbmNkY29uJGVtYmVkZGluZyA8LSBlbWJbbmFtZXMoY252LnNjKVtvcmRlcihjbnYuc2MpXSxdCmNvbCA8LSBwYWdvZGEyOjo6dmFsMmNvbChjbnYuc2MsZ3JhZGllbnQucmFuZ2UucXVhbnRpbGUgPSAwLjksZ3JhZGllbnRQYWxldHRlID0gY29sb3JSYW1wUGFsZXR0ZShjKHJlcCgnZ3JleTg1JywzKSwncmVkJykpKDEwMjQpKQpwMyA8LSBuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuMSxzaXplPTAuNSxjb2xvcnM9Y29sLHBsb3QubmE9RikKbmNkY29uJGVtYmVkZGluZyA8LSBlbWI7CnAzCmBgYAoKVGhyZXNob2xkLWJhc2VkLCB3aXRoIG92ZXJwbG90dGluZwpgYGB7ciBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD00fQpjb2wgPC0gY252LnNjPjAuNzsKI2NvbCA8LSBjbnYuc2M+MC44ZS0zOyAKZW1iIDwtbmNkY29uJGVtYmVkZGluZwpuY2Rjb24kZW1iZWRkaW5nIDwtIGVtYltuYW1lcyhjb2wpW29yZGVyKGNvbCldLF0KcDMgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjA1LHNpemU9MC42LGdyb3Vwcz1jb2wsbWFyay5ncm91cHM9RixyYXN0ZXI9VCxwYWxldHRlPWMoIlRSVUUiPSdyZWQnLCJGQUxTRSI9J2dyYXk5MCcpLHBsb3QubmE9RixyYXN0ZXIuaGVpZ2h0PTQscmFzdGVyLndpZHRoPTQpCm5jZGNvbiRlbWJlZGRpbmcgPC0gZW1iOwojcGRmKGZpbGU9J2Nudl9vdmVydmlldy5wZGYnLHdpZHRoPTQsaGVpZ2h0PTQpOyBwcmludChwMyk7IGRldi5vZmYoKTsKcDMKYGBgCgoKCgpDb21iaW5lZCBmaWd1cmUKYGBge3IgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9NX0KcDEgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LGdyb3Vwcz1uY2Rjb24kZ2V0RGF0YXNldFBlckNlbGwoKSxwbG90Lm5hPUYsbWFyay5ncm91cHM9RixwYWxldHRlPXNhbXBsZS5wYWwpCnAyIDwtIG5jZGNvbiRwbG90R3JhcGgoYWxwaGE9MC4wMyxzaXplPTAuNSxncm91cHM9YW5ub3QscGxvdC5uYT1GLG1hcmsuZ3JvdXBzPVQscGFsZXR0ZT1hbm5vdC5wYWxmKQojcDMgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LHBsb3QubmE9RixtYXJrLmdyb3Vwcz1ULGNsdXN0ZXJpbmc9J2xlaWRlbicpCiNwMyA8LSBuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuMDMsc2l6ZT0wLjUsY29sb3JzPXBhZ29kYTI6Ojp2YWwyY29sKGNudi5zYyxncmFkaWVudC5yYW5nZS5xdWFudGlsZSA9IDAuOSkscGxvdC5uYT1GKQpwbCA8LSBsaXN0KHAxLHAyLHAzKQpwbG90X2dyaWQocGxvdGxpc3Q9cGwsbnJvdz0xKQpgYGAKCmBgYHtyIGZpZy53aWR0aD00LGZpZy5oZWlnaHQ9Nn0KcmVxdWlyZShncmlkRXh0cmEpCnJhc3RlciA8LSBUOyBzaXplPTAuNTsgYWxwaGE9MC4wMzsKcDEgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT1hbHBoYSxzaXplPXNpemUscmFzdGVyPXJhc3Rlcixncm91cHM9bmNkY29uJGdldERhdGFzZXRQZXJDZWxsKCkscGxvdC5uYT1GLG1hcmsuZ3JvdXBzPUYscGFsZXR0ZT1zYW1wbGUucGFsLHJhc3Rlci5oZWlnaHQ9NSxyYXN0ZXIud2lkdGg9NSkKcDIgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT1hbHBoYSxzaXplPXNpemUscmFzdGVyPXJhc3Rlcixncm91cHM9YW5ub3QscGxvdC5uYT1GLG1hcmsuZ3JvdXBzPVQscGFsZXR0ZT1hbm5vdC5wYWxmLGZvbnQuc2l6ZT1jKDMsNSkscmFzdGVyLmhlaWdodD01LHJhc3Rlci53aWR0aD01KQojcDMgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT1hbHBoYSxzaXplPXNpemUscmFzdGVyPXJhc3Rlcixjb2xvcnM9Y29sLHBsb3QubmE9RikKcGwgPC0gbGlzdChwMSxwMixwMykKcGRmKGZpbGU9J292ZXJ2aWV3LnBkZicsd2lkdGg9NCxoZWlnaHQ9Nik7IApncmlkLmFycmFuZ2UocDIscDEscDMsbGF5b3V0X21hdHJpeD1yYmluZChjKDEsMSksYygyLDMpKSxoZWlnaHRzPWMoMiwxKSkKZGV2Lm9mZigpOwpgYGAKCmBgYHtyfQppbnZpc2libGUobGFwcGx5KDE6bGVuZ3RoKHBsKSxmdW5jdGlvbihpKSB7IHBkZihwYXN0ZSgncGFuZWwnLGksJ3BkZicsc2VwPScuJyksd2lkdGg9NCxoZWlnaHQ9NCk7IHByaW50KHBsW1tpXV0pOyBkZXYub2ZmKCl9KSkKYGBgCgoKIyMjIExvb2tpbmcgYXQgY2VsbHMgd2l0aCBTQ1AtbGlrZSBwbHVyaXBvdGVuY3kgZ2VuZXMKCgpgYGB7cn0KYXNjbDFlIDwtIGNvbm9zOjo6Z2V0R2VuZUV4cHJlc3Npb24obmNkY29uLCdBU0NMMScpCnBob3gyYmUgPC0gY29ub3M6OjpnZXRHZW5lRXhwcmVzc2lvbihuY2Rjb24sJ1BIT1gyQicpCnBycngyZSA8LSBjb25vczo6OmdldEdlbmVFeHByZXNzaW9uKG5jZGNvbiwnUFJSWDInKQpwcnJ4MWUgPC0gY29ub3M6OjpnZXRHZW5lRXhwcmVzc2lvbihuY2Rjb24sJ1BSUlgxJykKc294MTBlIDwtIGNvbm9zOjo6Z2V0R2VuZUV4cHJlc3Npb24obmNkY29uLCdTT1gxMCcpCmZveGQzZSA8LSBjb25vczo6OmdldEdlbmVFeHByZXNzaW9uKG5jZGNvbiwnRk9YRDMnKQoKZGMgPC0gc294MTBlPjAgJiBwaG94MmJlPjAKCgpgYGAKCgpgYGB7ciBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQojZGMgPC0gc294MTBlPjAgJiBhc2NsMWU+MApkYyA8LSBzb3gxMGU+MCAmIHBob3gyYmU+MApkYyA8LSBmb3hkM2U+MCAmIHBob3gyYmU+MApkYyA8LSBzb3gxMGU+MCAmIHBycngyZT4wCmRjIDwtIHNveDEwZT4wICYgcHJyeDFlPjAKZGMgPC0gcGhveDJiZT4wICYgcHJyeDFlPjAKZGMgPC0gcGhveDJiZT4wLjMgJiBwcnJ4MWU+MC4zICYgc294MTBlPjAKdGFibGUoZGMpCm5hbWVzKGRjKVtkY10KZW0gPC0gbmNkY29uJHNhbXBsZXMkTkIyNiRlbWJlZGRpbmdzJFBDQVtbMl1dCnBsb3QoZW1bLDFdLGVtWywyXSxwY2g9MTksY2V4PTAuMixjb2w9YWRqdXN0Y29sb3IoMSxhbHBoYT0wLjAxKSkKdmMgPC0gbmFtZXMoZGMpW2RjXQpwb2ludHMoZW1bcm93bmFtZXMoZW0pICVpbiUgdmMsMV0sZW1bcm93bmFtZXMoZW0pICVpbiUgdmMsMl0sY29sPTIpCmBgYAoKTG9vayBhdCBzY2F0dGVyIHBsb3RzIG9mIGtleSBmb3JrIG1hcmtlcnMKYGBge3J9CmducyA8LSBjKCdTT1gxMCcsJ0FTQ0wxJywnUEhPWDJCJywnUFJSWDInLCdQUlJYMScsJ0ZPWEQzJywnUE9TVE4nKQpnbnNlIDwtIGxhcHBseShwYWdvZGEyOjo6c24oZ25zKSxmdW5jdGlvbihnKSBjb25vczo6OmdldEdlbmVFeHByZXNzaW9uKG5jZGNvbixnKSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NX0KZzEgPC0gJ1NPWDEwJzsgZzIgPC0nUEhPWDJCJwpnMSA8LSAnUE9TVE4nOyBnMiA8LSdQSE9YMkInCiNnMSA8LSAnU09YMTAnOyBnMiA8LSdQUlJYMicgIyAxIGNlbGwKI2cxIDwtICdTT1gxMCc7IGcyIDwtJ1BSUlgxJyAjIGNsZWFyCiNnMSA8LSAnU09YMTAnOyBnMiA8LSdBU0NMMScgIyBub25lCiNnMSA8LSAnRk9YRDMnOyBnMiA8LSAnUEhPWDJCJwojZzEgPC0gJ1BSUlgxJzsgZzIgPC0gJ1BIT1gyQicKCmRjIDwtIGduc2VbW2cxXV0+MCAmIGduc2VbW2cyXV0+MAp2YyA8LSBuYW1lcyhkYylbZGNdOyB2YyA8LSB2Y1tncmVwbCgnXk5CJyx2YyldCmRmIDwtIGRhdGEuZnJhbWUoZ25zZVtbZzFdXVt2Y10sZ25zZVtbZzJdXVt2Y10pOyBjb2xuYW1lcyhkZikgPC0gYyhnMSxnMikKZGYgPC0gY2JpbmQoZGYsZGF0YS5mcmFtZSh0eXBlPWFubm90W3ZjXSxjZWxsPXZjLGRvdWJsZXQ9ZG91YmxldFNjb3Jlc1t2Y10+MC4yLGluZW1iPXZjICVpbiUgcm93bmFtZXMobmNkY29uJGVtYmVkZGluZykpKQpwMSA8LSBnZ3Bsb3QoZGYsYWVzXyh4PWFzLm5hbWUoZzEpLHk9YXMubmFtZShnMiksY29sb3VyPXF1b3RlKHR5cGUpKSkgKyBnZW9tX3BvaW50KHNpemU9MikgKyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWFubm90LnBhbCkKcDIgPC0gZ2dwbG90KGRmLGFlc18oeD1hcy5uYW1lKGcxKSx5PWFzLm5hbWUoZzIpLGNvbG91cj1xdW90ZShkb3VibGV0KSxzaGFwZT1xdW90ZShpbmVtYikpKSArIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiRkFMU0UiPSJncmF5MzAiLCAiVFJVRSI9InJlZCIpKTsKY293cGxvdDo6cGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QocDEscDIpKQpgYGAKCk1ha2UgYSBwYW5lbCBvZiBwYWlycwoKYGBge3J9CnBsIDwtIGxpc3QoYygnU09YMTAnLCdQSE9YMkInKSxjKCdTT1gxMCcsJ1BSUlgxJyksYygnU09YMTAnLCdQUlJYMicpLGMoJ1BSUlgxJywnUEhPWDJCJyksYygnUE9TVE4nLCdQSE9YMkInKSkKcHAgPC0gbGFwcGx5KHBsLGZ1bmN0aW9uKHgpIHsKICBnMSA8LSB4WzFdOyBnMiA8LSB4WzJdCiAgZGMgPC0gZ25zZVtbZzFdXT4wICYgZ25zZVtbZzJdXT4wCiAgdmMgPC0gbmFtZXMoZGMpW2RjXTsgdmMgPC0gdmNbZ3JlcGwoJ15OQicsdmMpXQogIGRmIDwtIGRhdGEuZnJhbWUoZ25zZVtbZzFdXVt2Y10sZ25zZVtbZzJdXVt2Y10pOyBjb2xuYW1lcyhkZikgPC0gYyhnMSxnMikKICBkZiA8LSBjYmluZChkZixkYXRhLmZyYW1lKHR5cGU9YW5ub3RbdmNdLGNlbGw9dmMsZG91YmxldD1kb3VibGV0U2NvcmVzW3ZjXT4wLjIsaW5lbWI9dmMgJWluJSByb3duYW1lcyhuY2Rjb24kZW1iZWRkaW5nKSkpCiAgcDEgPC0gZ2dwbG90KGRmLGFlc18oeD1hcy5uYW1lKGcxKSx5PWFzLm5hbWUoZzIpLGNvbG91cj1xdW90ZSh0eXBlKSkpICsgZ2VvbV9wb2ludChzaXplPTIpICsgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1hbm5vdC5wYWwpCn0pCmNvd3Bsb3Q6OnBsb3RfZ3JpZChwbG90bGlzdD1wcCxuY29sPTIpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CnZjIDwtIG5hbWVzKGRjKVtkY107IHZjIDwtIHZjW2dyZXBsKCdeTkInLHZjKV0KZGYgPC0gZGF0YS5mcmFtZShwaG94MmI9cGhveDJiZVt2Y10scHJyeDE9cHJyeDFlW3ZjXSx0eXBlPWFubm90W3ZjXSxjZWxsPXZjKQpnZ3Bsb3QoZGYsYWVzKHg9cGhveDJiLHk9cHJyeDEsY29sb3VyPXR5cGUpKSArIGdlb21fcG9pbnQoKQpgYGAKCgoKYGBge3IgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9NX0KZGMgPC0gcGhveDJiZT4wICYgcHJyeDFlPjAKZGMgPC0gcGhveDJiZT4wICYgcHJyeDFlPjAKZGMgPC0gcGhveDJiZT4wLjMgJiBwcnJ4MWU+MC4zICYgc294MTBlPjAKdmMgPC0gbmFtZXMoZGMpW2RjXTsgdmMgPC0gdmNbZ3JlcGwoJ15OQicsdmMpXQoKcDEgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LGdyb3Vwcz1hbm5vdCxwbG90Lm5hPUYsbWFyay5ncm91cHM9VCkKcDIgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LGdlbmU9J1NPWDEwJyxwbG90Lm5hPUYsbWFyay5ncm91cHM9VCkKcDMgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjIsc2l6ZT0zLGdyb3Vwcz1zZXROYW1lcyhyZXAoMSxsZW5ndGgodmMpKSx2YykscGxvdC5uYT1GLG1hcmsuZ3JvdXBzPUYpK3hsaW0ocmFuZ2UobmNkY29uJGVtYmVkZGluZ1ssMV0pKSAreWxpbShyYW5nZShuY2Rjb24kZW1iZWRkaW5nWywyXSkpCnBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHAxLHAyLHAzKSxucm93PTEpCmBgYAoKCiMjIEFubm90YXRpb24gb2YgdGhlIGFkcmVuYWwgZGF0YQoKYGBge3J9Cm5ld3dfYW5ub3QgPC0gcmVhZFJEUygifnBraGFyY2hlbmtvL20vbmluaWIvTkIvbmV3d19hbm5vdC5yZHMiKQpgYGAKCmBgYHtyfQphcDIgPC0gbmNkY29uJHNhbXBsZXMkQWRyCmFwMiRnZXRFbWJlZGRpbmcodHlwZT0nUENBJyxlbWJlZGRpbmdUeXBlPSd0U05FJyxuLmNvcmVzPTMwKQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9NSxmaWcuaGVpZ2h0PTV9CmFwMiRwbG90RW1iZWRkaW5nKHR5cGU9J1BDQScsZ3JvdXBzPW5ld3dfYW5ub3QsZW1iZWRkaW5nVHlwZSA9ICd0U05FJyxtYXJrLmNsdXN0ZXJzID0gVCxjZXg9MC4yLG1hcmsuY2x1c3Rlci5jZXggPSAxKQpgYGAKCmBgYHtyfQphcDIkZ2V0S25uQ2x1c3RlcnModHlwZT0nUENBJyxtZXRob2Q9bGVpZGVuLmNvbW11bml0eSxyPTEpCmBgYAoKYGBge3IgZmlnLndpZHRoPTUsZmlnLmhlaWdodD01fQphcDIkcGxvdEVtYmVkZGluZyh0eXBlPSdQQ0EnLGVtYmVkZGluZ1R5cGUgPSAndFNORScsbWFyay5jbHVzdGVycyA9IFQsY2V4PTAuMixtYXJrLmNsdXN0ZXIuY2V4ID0gMS41KQpgYGAKCndyaXRlIG91dCBwMiBhcHAKYGBge3J9CnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShvcmcuSHMuZWcuZGIpKQppZHMgPC0gdW5saXN0KGxhcHBseShtZ2V0KGNvbG5hbWVzKGFwMiRjb3VudHMpLG9yZy5Icy5lZ0FMSUFTMkVHLGlmbm90Zm91bmQ9TkEpLGZ1bmN0aW9uKHgpIHhbMV0pKQpyaWRzIDwtIG5hbWVzKGlkcyk7IG5hbWVzKHJpZHMpIDwtIGlkczsKIyBsaXN0IGFsbCB0aGUgaWRzIHBlciBHTyBjYXRlZ29yeSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApnby5lbnYgPC0gbGlzdDJlbnYoZWFwcGx5KG9yZy5Icy5lZ0dPMkFMTEVHUyxmdW5jdGlvbih4KSBhcy5jaGFyYWN0ZXIobmEub21pdChyaWRzW3hdKSkpKQphcDIkdGVzdFBhdGh3YXlPdmVyZGlzcGVyc2lvbihnby5lbnYsdmVyYm9zZT1ULGNvcnJlbGF0aW9uLmRpc3RhbmNlLnRocmVzaG9sZD0wLjk1LHJlY2FsY3VsYXRlLnBjYT1GLHRvcC5hc3BlY3RzPTE1KQoKbGlicmFyeShHTy5kYikKdGVybURlc2NyaXB0aW9ucyA8LSBUZXJtKEdPVEVSTVtuYW1lcyhnby5lbnYpXSk7ICMgc2F2ZXMgYSBnb29kIG1pbnV0ZSBvciBzbyBjb21wYXJlZCB0byBpbmRpdmlkdWFsIGxvb2t1cHMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCnNuIDwtIGZ1bmN0aW9uKHgpIHsgbmFtZXMoeCkgPC0geDsgeH0KZ2VuZVNldHMgPC0gbGFwcGx5KHNuKG5hbWVzKGdvLmVudikpLGZ1bmN0aW9uKHgpIHsKICBsaXN0KHByb3BlcnRpZXM9bGlzdChsb2NrZWQ9VCxnZW5lc2V0bmFtZT14LHNob3J0ZGVzY3JpcHRpb249YXMuY2hhcmFjdGVyKHRlcm1EZXNjcmlwdGlvbnNbeF0pKSxnZW5lcz1jKGdvLmVudltbeF1dKSkKfSkKYWRyLnAyYXBwIDwtIG1ha2UucDIuYXBwKGFwMiwgZGVuZHJvZ3JhbUNlbGxHcm91cHMgPSBhcDIkY2x1c3RlcnMkUENBJG11bHRpbGV2ZWwsIGdlbmVTZXRzID0gZ2VuZVNldHMsaW5uZXJPcmRlcj0nb2RQQ0EnKTsKYWRyLnAyYXBwJHNlcmlhbGl6ZVRvU3RhdGljRmFzdChiaW5hcnkuZmlsZW5hbWUgPSAnYWRyLnAyLmFwcC5iaW4nLHZlcmJvc2U9VCkKYGBgCgoKCkdldCBtYXJrZXJzCmBgYHtyfQpkZSA8LSBhcDIkZ2V0RGlmZmVyZW50aWFsR2VuZXModHlwZT0nUENBJyxuLmNvcmVzPTMwLGFwcGVuZC5hdWM9VFJVRSx6LnRocmVzaG9sZD0wLHVwcmVndWxhdGVkLm9ubHk9VCkKYGBgCgpgYGB7ciBmaWcud2lkdGg9NyxmaWcuaGVpZ2h0PTEyfQpzb3VyY2UoIn4vbS9wMi9jb25vcy9SL3Bsb3QuUiIpCmZhYyA8LSBhcDIkY2x1c3RlcnMkUENBW1sxXV0KcHAgPC0gcGxvdERFaGVhdG1hcChhcDIsZmFjLGRlLG4uZ2VuZXMucGVyLmNsdXN0ZXIgPSAxMCAsc2hvdy5nZW5lLmNsdXN0ZXJzPVQscm93LmxhYmVsLmZvbnQuc2l6ZSA9IDYpCnBkZihmaWxlPSdhZHIuaGVhdG1hcC5wZGYnLHdpZHRoPTcsaGVpZ2h0PTE1KTsgcHJpbnQocHApOyBkZXYub2ZmKCk7CnBwCmBgYAoKCgpgYGB7cn0KbmNkY29uQSA8LSByZWFkUkRTKCJ+cGtoYXJjaGVua28vbS9uaW5pYi9OQi9uY2Rjb25BLnJkcyIpCmBgYAoKCgpgYGB7ciBmaWcud2lkdGg9MTUsZmlnLmhlaWdodD01fQpjZiA8LSB1bmxpc3QobGlzdChhcy5mYWN0b3Ioc2V0TmFtZXMocmVwKCdOQicsbGVuZ3RoKG5mYWMpKSxuYW1lcyhuZmFjKSkpLG5ld3dfYW5ub3QpKQpwMSA8LSBuY2Rjb25BJHBsb3RHcmFwaChhbHBoYT0wLjA1LHNpemU9MC44LGdyb3Vwcz1jZixwbG90Lm5hPUYsZm9udC5zaXplPTMscGFsZXR0ZT1mdW5jdGlvbihuKSBjKCdncmF5OTUnLHJhaW5ib3cobi0xLHY9MSkpKQpmYWMgPC0gYXAyJGNsdXN0ZXJzJFBDQVtbMV1dCmNmIDwtIHVubGlzdChsaXN0KGFzLmZhY3RvcihzZXROYW1lcyhyZXAoJ05CJyxsZW5ndGgobmZhYykpLG5hbWVzKG5mYWMpKSksZmFjKSkKcDMgPC0gbmNkY29uQSRwbG90R3JhcGgoYWxwaGE9MC4wNSxzaXplPTAuOCxncm91cHM9Y2YscGxvdC5uYT1GLGZvbnQuc2l6ZT0zLHBhbGV0dGU9ZnVuY3Rpb24obikgYygnZ3JheTk1JyxyYWluYm93KG4tMSx2PTEpKSkKcDIgPC0gbmNkY29uQSRwbG90R3JhcGgoYWxwaGE9MC4wMyxzaXplPTAuNSxncm91cHM9bmZhYyxwbG90Lm5hPUYsZm9udC5zaXplPTMpCnBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHAxLHAzLHAyKSxucm93PTEpCmBgYAoKIyMgTGluZWFyIGRldmlhdGlvbiB0ZXN0cwoKYGBge3IgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9NX0KcDEgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LGdyb3Vwcz1uY2Rjb24kZ2V0RGF0YXNldFBlckNlbGwoKSxwbG90Lm5hPUYsbWFyay5ncm91cHM9RixwYWxldHRlPXNhbXBsZS5wYWwpCnAyIDwtIG5jZGNvbiRwbG90R3JhcGgoYWxwaGE9MC4wMyxzaXplPTAuNSxncm91cHM9YW5ub3QscGxvdC5uYT1GLG1hcmsuZ3JvdXBzPVQscGFsZXR0ZT1hbm5vdC5wYWxmKQojcDMgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LHBsb3QubmE9RixtYXJrLmdyb3Vwcz1ULGNsdXN0ZXJpbmc9J2xlaWRlbicpCnAzIDwtIG5jZGNvbiRwbG90R3JhcGgoYWxwaGE9MC4wMyxzaXplPTAuNSxncm91cHM9ZGFubm90LHBsb3QubmE9RixtYXJrLmdyb3Vwcz1ULHBhbGV0dGU9ZGFubm90LnBhbGYpCnBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHAxLHAyLHAzKSxucm93PTEpCmBgYAoKYGBge3IgZmlnLndpZHRoPTcsZmlnLmhlaWdodD01fQpkZiA8LSBkYXRhLmZyYW1lKG5jZGNvbiRlbWJlZGRpbmcpCmRmJHR5cGUgPC0gYW5ub3Rbcm93bmFtZXMoZGYpXQpjb2xuYW1lcyhkZikgPC0gYygneCcsJ3knLCd0JykKZ2dwbG90KGRmLGFlcyh4LHksY29sb3I9dCkpK2dlb21fcG9pbnQoc2l6ZT0wLjEsYWxwaGE9MC4xKSArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoLTE4LC0xMywtMywxMCksbGluZXR5cGU9MykKYGBgCgpgYGB7cn0KY2VsbHMgPC0gaW50ZXJzZWN0KG5hbWVzKGFubm90KVthbm5vdCAlaW4lIGMoIk1lc2VuY2h5bWFsIiwnU0NQLWxpa2UnLCdOb3JhZHJlbmVyZ2ljJyldLCByb3duYW1lcyhuY2Rjb24kZW1iZWRkaW5nKVtuY2Rjb24kZW1iZWRkaW5nWywxXSA8MTAgJiBuY2Rjb24kZW1iZWRkaW5nWywxXSA+IC0xMyAmIG5jZGNvbiRlbWJlZGRpbmdbLDJdPCAtMTddKQpgYGAKCmBgYHtyfQptZXMuY2VsbHMgPC0gaW50ZXJzZWN0KG5hbWVzKGFubm90KVthbm5vdCAlaW4lIGMoIk1lc2VuY2h5bWFsIiwnU0NQLWxpa2UnLCdOb3JhZHJlbmVyZ2ljJyldLCByb3duYW1lcyhuY2Rjb24kZW1iZWRkaW5nKVtuY2Rjb24kZW1iZWRkaW5nWywxXSA8IC0zICYgbmNkY29uJGVtYmVkZGluZ1ssMV0gPiAtMTggJiBuY2Rjb24kZW1iZWRkaW5nWywyXTwgLTE3XSkgCmBgYAoKClByaW5jaXBhbCBjdXJ2ZSBmaXQKCmBgYHtyfQpmaXQucGMucHNldWRvdGltZSA8LSBmdW5jdGlvbihjb24sIGNlbGxzLG5kaW1zPTEwLGVtYmVkZGluZz1OVUxMLCByZXR1cm4uZGV0YWlscz1GKSB7CiAgcmVxdWlyZShwcmluY3VydmUpCiAgaWYoaXMubnVsbChlbWJlZGRpbmcpKSB7CiAgICBpZihpcy5udWxsKGNvbiRtaXNjJGVtYmVkZGluZ3MpIHx8IGlzLm51bGwoY29uJG1pc2MkZW1iZWRkaW5ncyRsaW5maXQpKSB7CiAgICAgIG9sZC5lbWIgPC0gY29uJGVtYmVkZGluZzsKICAgICAgZW1iZWRkaW5nIDwtIGNvbiRtaXNjJGVtYmVkZGluZ3MkbGluZml0IDwtIGNvbiRlbWJlZEdyYXBoKG1ldGhvZD0nbGFyZ2VWaXMnLHRhcmdldC5kaW1zPW5kaW1zKQogICAgICBjb24kZW1iZWRkaW5nIDwtIG9sZC5lbWI7CiAgICB9IGVsc2UgewogICAgICBlbWJlZGRpbmcgPC0gY29uJG1pc2MkZW1iZWRkaW5ncyRsaW5maXQKICAgIH0gCiAgfQogIGVtYmVkZGluZyA8LSBlbWJlZGRpbmdbcm93bmFtZXMoZW1iZWRkaW5nKSAlaW4lIGNlbGxzLF0KICB4IDwtIHByaW5jaXBhbF9jdXJ2ZShlbWJlZGRpbmcpCiAgaWYocmV0dXJuLmRldGFpbHMpIHsgCiAgICAjIGlkZW50aWZ5IGNsb3Nlc3QgY2VsbCBmb3IgZWFjaCBwcmluY2lwYWwgcG9pbnQKICAgIGVuIDwtIGNvbm9zOjo6bjJDcm9zc0tubih4JHMsZW1iZWRkaW5nLDMsdmVyYm9zZT1GLGluZGV4VHlwZT0nTDInKQogICAgY29sbmFtZXMoZW4pIDwtIG5hbWVzKHgkbGFtYmRhKTsgcm93bmFtZXMoZW4pIDwtIHJvd25hbWVzKGVtYmVkZGluZykKICAgIGVuQHggPC0gZXhwKG1lYW4oZW5AeCkvZW5AeC8xMCkKICAgIGVuIDwtIHQoZW4pL2NvbFN1bXMoZW4pCiAgICB4JGVuIDwtIGVuOwogICAgeCRlbWJlZGRpbmcgPC0gZW1iZWRkaW5nOwogICAgcmV0dXJuICh4KQogIH0KICB4JGxhbWJkYQp9CmBgYAoKYGBge3J9CnNldC5zZWVkKDApCnBjdXJ2ZSA8LSBmaXQucGMucHNldWRvdGltZShuY2Rjb24sY2VsbHMsbmRpbXM9NSxyZXR1cm4uZGV0YWlscz1UKQpwdCA8LSBwY3VydmUkbGFtYmRhCmBgYAoKYGBge3J9CnB0LnBvcyA8LSBuY2Rjb24kZW1iZWRkaW5nW25hbWVzKHB0KVtvcmRlcihwdCldLF0gJT4lIHpvbzo6cm9sbGFwcGx5KDUwMCxtZWRpYW4pICU+JSBkYXRhLmZyYW1lKCkKY29sbmFtZXMocHQucG9zKSA8LSBjKCd4JywneScpOwpwdC5wb3Mkc2QgPC0gbmNkY29uJGVtYmVkZGluZ1tuYW1lcyhwdClbb3JkZXIocHQpXSxdICU+JSB6b286OnJvbGxhcHBseSg1MDAsc2QpICU+JSBhcHBseSgxLHN1bSkKYGBgCgpBc3BlY3QgcmF0aW9uIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgc2VsZWN0ZWQgc3Vic2V0IGFuZCB0aGUgYWN0dWFsIG9uZQpgYGB7cn0KUmVkdWNlKGV2YWwoJy8nKSxhcHBseShuY2Rjb24kZW1iZWRkaW5nLDIsZnVuY3Rpb24oeCkgZGlmZihyYW5nZSh4KSkpKS9SZWR1Y2UoZXZhbCgnLycpLGFwcGx5KG5jZGNvbiRlbWJlZGRpbmdbY2VsbHMsXSwyLGZ1bmN0aW9uKHgpIGRpZmYocmFuZ2UoeCkpKSkKYGBgCgoKYGBge3IgZmlnLndpZHRoPTEsZmlnLmhlaWdodD0wLjk0fQojIGVzdGltYXRlIHByaW5jaXBhbCBjdXJ2ZSBwb3NpdGlvbnMKcDEgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjIsc2l6ZT0wLjIsY29sb3JzPXB0LW1lYW4ocHQpLHBsb3QubmE9RixncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk1KSArCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpKSsKICBnZW9tX2xpbmUoZGF0YT1wdC5wb3NbMTozLjJlMyxdLGFlcyh4LHkpLGFscGhhPTAuOCxzaXplPTAuNSkgCnAxCmBgYAoKRm9jdXNlZCBicmlkZ2UgYW5kIENOViBwaWN0dXJlIGZvciBmaWcuMgoKYGBge3IgZmlnLndpZHRoPTEsIGZpZy5oZWlnaHQ9Mn0KcDAgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjIsc2l6ZT0wLjIsZ3JvdXBzPWFubm90W2NlbGxzXSxwYWxldHRlPWFubm90LnBhbCxtYXJrLmdyb3Vwcz1GLHBsb3QubmE9RixncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk1LHJhc3Rlcj1ULHJhc3Rlci53aWR0aD0xLjUscmFzdGVyLmhlaWdodD0xLjUpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArIAogICNnZW9tX2xhYmVsKHg9LUluZix5PUluZix2anVzdD0xLGhqdXN0PTAsbGFiZWw9J2NsdXN0ZXJzJyxkYXRhPWRhdGEuZnJhbWUoeD1jKDEpKSxsYWJlbC5zaXplPTApICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpKQoKY29sIDwtIGNudi5zYz4wLjc7IAplbWIgPC0gbmNkY29uJGVtYmVkZGluZwpuY2Rjb24kZW1iZWRkaW5nIDwtIGVtYltuYW1lcyhjb2wpW29yZGVyKGNvbCldLF0KcDMgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjA1LHNpemU9MC4zLGdyb3Vwcz1jb2xbY2VsbHNdLG1hcmsuZ3JvdXBzPUYscmFzdGVyPVQscGFsZXR0ZT1jKCJUUlVFIj0ncmVkJywiRkFMU0UiPSdncmF5OTAnKSxwbG90Lm5hPUYscmFzdGVyLmhlaWdodD0xLjUscmFzdGVyLndpZHRoPTEuNSkrCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKyAKICBnZW9tX2xhYmVsKHg9LUluZix5PS1JbmYsdmp1c3Q9MCxoanVzdD0wLGxhYmVsPScoQ05WKScsZGF0YT1kYXRhLmZyYW1lKHg9YygxKSksbGFiZWwuc2l6ZT0wKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSkKbmNkY29uJGVtYmVkZGluZyA8LSBlbWI7CiNwZGYoZmlsZT0nY252X292ZXJ2aWV3LnBkZicsd2lkdGg9NCxoZWlnaHQ9NCk7IHByaW50KHAzKTsgZGV2Lm9mZigpOwpwbG90X2dyaWQocGxvdGxpc3Q9bGlzdChwMCxwMyksbmNvbD0xKQpgYGAKYGBge3IgZmlnLndpZHRoPTEsZmlnLmhlaWdodD00fQpnbCA8LSByZXYoYygiUzEwMEIiLCJTT1gxMCIpKQpwbCA8LSBsYXBwbHkoZ2wsZnVuY3Rpb24oZykgbmNkY29uJHBsb3RHcmFwaChjb2xvcnM9Y29ub3M6OjpnZXRHZW5lRXhwcmVzc2lvbihuY2Rjb24sZylbY2VsbHNdLGFscGhhPTAuMyxzaXplPTAuMSxwbG90Lm5hPUYsZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC45OSxyYXN0ZXI9VCxyYXN0ZXIud2lkdGg9MS41LHJhc3Rlci5oZWlnaHQ9MS41KSsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArIAogIGdlb21fbGFiZWwoeD0tSW5mLHk9LUluZix2anVzdD0wLGhqdXN0PTAsbGFiZWw9ZyxkYXRhPWRhdGEuZnJhbWUoeD1jKDEpKSxsYWJlbC5zaXplPTApICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpKSkKcHAgPC0gcGxvdF9ncmlkKHBsb3RsaXN0PWMobGlzdChwMCxwMykscGwpLG5jb2w9MSkKcHAKYGBgCgpgYGB7cn0KcGRmKGZpbGU9J25iLmJyaWRnZS5wZGYnLHdpZHRoPTEsaGVpZ2h0PTEqMC45NCo0KTsgcHJpbnQocHApOyBkZXYub2ZmKCk7CmBgYAoKIyMjIEZvciBpbGx1c3RyYXRpb24KCkZvciB0aGUgbm9yYWRyZW5lcmdpYyBzaWRlOiBQSE9YMkIsIExNTzEsIFRICkZvciB0aGUgbWVzZW5jaHltYWwgc2lkZTogUFJSWDEKCgpgYGB7cn0KcDAgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjIsc2l6ZT0wLjIsZ3JvdXBzPWFubm90W2NlbGxzXSxwYWxldHRlPWFubm90LnBhbCxtYXJrLmdyb3Vwcz1GLHBsb3QubmE9RixncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk1LHJhc3Rlcj1ULHJhc3Rlci53aWR0aD0xLjUscmFzdGVyLmhlaWdodD0xLjUpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArIAogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCkpKwogIGdlb21fbGluZShkYXRhPXB0LnBvc1sxOjMuMmUzLF0sYWVzKHgseSksYWxwaGE9MC44LHNpemU9MC41KQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9MSxmaWcuaGVpZ2h0PTR9CmdsIDwtIHJldihjKCJQSE9YMkIiLCJQUlJYMSIsIlNPWDEwIikpCnBsIDwtIGxhcHBseShnbCxmdW5jdGlvbihnKSBuY2Rjb24kcGxvdEdyYXBoKGNvbG9ycz1jb25vczo6OmdldEdlbmVFeHByZXNzaW9uKG5jZGNvbixnKVtjZWxsc10sYWxwaGE9MC4zLHNpemU9MC4xLHBsb3QubmE9RixncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk5LHJhc3Rlcj1ULHJhc3Rlci53aWR0aD0xLjUscmFzdGVyLmhlaWdodD0xLjUpKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsgCiAgZ2VvbV9sYWJlbCh4PS1JbmYseT0tSW5mLHZqdXN0PTAsaGp1c3Q9MCxsYWJlbD1nLGRhdGE9ZGF0YS5mcmFtZSh4PWMoMSkpLGxhYmVsLnNpemU9MCkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCkpKQpwcCA8LSBwbG90X2dyaWQocGxvdGxpc3Q9YyhsaXN0KHAwKSxwbCksbmNvbD0xKQpwcApgYGAKCmBgYHtyfQpwZGYoZmlsZT0nbmIuZm9yay5nZW5lczIucGRmJyx3aWR0aD0xLGhlaWdodD0xKjAuOTQqNCk7IHByaW50KHBwKTsgZGV2Lm9mZigpOwpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTEsZmlnLndpZHRoPTF9Cm5jZGNvbiRwbG90R3JhcGgoY29sb3JzPWNvbm9zOjo6Z2V0R2VuZUV4cHJlc3Npb24obmNkY29uLCdTT1gxMCcpW2NlbGxzXSxhbHBoYT0wLjMsc2l6ZT0wLjEscGxvdC5uYT1GLGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOTk5LHJhc3Rlcj1ULHJhc3Rlci53aWR0aD0xLjUscmFzdGVyLGhlaWdodD0xLjUpICtnZW9tX2xhYmVsKHg9LUluZix5PS1JbmYsdmp1c3Q9MCxoanVzdD0wLGxhYmVsPSdTT1gxMCcsaW5oZXJpdC5hZXMgPUYsZGF0YT1kYXRhLmZyYW1lKHg9YygxKSksbGFiZWwuc2l6ZT0wKQpgYGAKCgoKClRlc3QgZm9yIGFzc29jaWF0ZWQgZ2VuZXMKCmBgYHtyfQptYXQgPC0gbmNkY29uJGdldEpvaW50Q291bnRNYXRyaXgoKQptYXQgPC0gdChtYXRbbmFtZXMocHQpW29yZGVyKHB0KVsxOjMuMmUzXV0sXSkKYGBgCgpgYGB7cn0KIyBGIHRlc3QgY29tcGFyaW5nIGdsbSBtb2RlbHMgb2YgZXhwcmVzc2lvbiB3aXRoIGFuZCB3aXRob3V0IHBzZXVkb3RpbWUKdGVzdC5hc3NvY2lhdGVkLmdlbmVzIDwtIGZ1bmN0aW9uKHB0LG1hdCxzcGxpbmUuZGY9MyxuLmNvcmVzPTMyKSB7CiAgbWF0IDwtIG1hdFssY29sbmFtZXMobWF0KSAlaW4lIG5hbWVzKHB0KV0KICBkZiA8LSBkYXRhLmZyYW1lKGRvLmNhbGwocmJpbmQsbWNsYXBwbHkoc2V0TmFtZXMoMTpucm93KG1hdCkscm93bmFtZXMobWF0KSksZnVuY3Rpb24oaSkgewogICAgZXhwIDwtIG1hdFtpLF0KICAgIHNkZiA8LSBkYXRhLmZyYW1lKGV4cD1leHAsdD1wdFtjb2xuYW1lcyhtYXQpXSkKICAgICMgbW9kZWwKICAgIG0gPC0gbWdjdjo6Z2FtKGV4cH5zKHQsaz1zcGxpbmUuZGYpLGRhdGE9c2RmLGZhbWlsbHk9Z2F1c3NpYW4oKSkKICAgICMgYmFja2dyb3VuZAogICAgbTAgPC0gbWdjdjo6Z2FtKGV4cH4xLGRhdGE9c2RmLGZhbWlsbHk9Z2F1c3NpYW4oKSkKICAgIGZzdGF0IDwtIDA7IAogICAgaWYobSRkZXZpYW5jZT4wKSB7CiAgICAgIGZzdGF0IDwtIChkZXZpYW5jZShtMCkgLSBkZXZpYW5jZShtKSkvKGRmLnJlc2lkdWFsKG0wKS1kZi5yZXNpZHVhbChtKSkvKGRldmlhbmNlKG0pL2RmLnJlc2lkdWFsKG0pKQogICAgfQogICAgcHZhbCA8LSAgcGYoZnN0YXQsZGYucmVzaWR1YWwobTApLWRmLnJlc2lkdWFsKG0pLGRmLnJlc2lkdWFsKG0pLGxvd2VyLnRhaWwgPSBGQUxTRSk7CiAgICByZXR1cm4oYyhwdmFsPXB2YWwsQT1kaWZmKHJhbmdlKHByZWRpY3QobSkpKSkpCiAgfSxtYy5jb3Jlcz1uLmNvcmVzLG1jLnByZXNjaGVkdWxlPVQpKSkKICBkZiRnZW5lIDwtIHJvd25hbWVzKG1hdCkKICBkZiRmZHIgPC0gcC5hZGp1c3QoZGYkcHZhbCk7CiAgZGYKfQpgYGAKCmBgYHtyfQpwdC5nZW5lcyA8LSB0ZXN0LmFzc29jaWF0ZWQuZ2VuZXMocHQsbWF0KQpwdC5nZW5lcyA8LSBhcnJhbmdlKHB0LmdlbmVzLHB2YWwpCmBgYAoKCkhlYXRtYXBzCgoKYGBge3J9CnJlc2NhbGUuYW5kLmNlbnRlciA8LSBmdW5jdGlvbih4LCBjZW50ZXI9RiwgbWF4LnF1YW50aWxlPTAuOTkpIHsKICBteCA8LSBxdWFudGlsZShhYnMoeCksbWF4LnF1YW50aWxlKSAjIGFic29sdXRlIG1heGltdW0KICBpZihteD09MCkgbXg8LW1heChhYnMoeCkpICMgaW4gY2FzZSB0aGUgcXVhbnRpbGUgc3F1YXNoZXMgYWxsIHRoZSBzaWduYWwKICB4W3g+bXhdIDwtIG14OyB4W3g8IC0xKm14XSA8LSAtMSpteDsgIyB0cmltCiAgaWYoY2VudGVyKSB4IDwtIHgtbWVhbih4KSAjIGNlbnRlcgogIHgvbWF4KGFicyh4KSk7ICMgc2NhbGUKfQpgYGAKCmBgYHtyfQpnbnMgPC0gcHQuZ2VuZXMkZ2VuZVtwdC5nZW5lcyRmZHI8MWUtNSAmIHB0LmdlbmVzJEE+MWUtMl0KZ25zIDwtIHB0LmdlbmVzJGdlbmVbcHQuZ2VuZXMkQT4xZS0yXVsxOjEwMDBdCgojZ25zIDwtIHB0LmdlbmVzJGdlbmVbcHQuZ2VuZXMkZmRyPjFlLTIgJiBwdC5nZW5lcyRBPjFlLTJdCmBgYAoKYGBge3J9CmdtIDwtIHpvbzo6cm9sbGFwcGx5KGFzLm1hdHJpeCh0KG1hdFtyZXYoZ25zKSxdKSksMzAsbWVhbixhbGlnbj0nbGVmdCcscGFydGlhbD1UKSAlPiUgYXBwbHkoMixyZXNjYWxlLmFuZC5jZW50ZXIsbWF4LnF1YW50aWxlPTEtMWUtMykgJT4lIHQKY29sbmFtZXMoZ20pIDwtIGNvbG5hbWVzKG1hdCkKIyBjbHVzdGVyCiNnbS5oY2wgPC0gaGNsdXN0KGFzLmRpc3QoMi1jb3IodChnbSkpKSxtZXRob2Q9J3dhcmQuRDInKQpnbS5oY2wgPC0gaGNsdXN0KGRpc3QoZ20pLG1ldGhvZD0nd2FyZC5EMicpCmdtLmhjbC5jbHVzdCA8LSBjdXRyZWUoZ20uaGNsLDQwKQpnbSA8LSBnbVtnbS5oY2wkb3JkZXIsXQpgYGAKCgpgYGB7ciB9CnJlcXVpcmUoQ29tcGxleEhlYXRtYXApCmhhID0gSGVhdG1hcEFubm90YXRpb24oCiAgY2VsbHM9YW5ub3RbY29sbmFtZXMoZ20pXSwgIAogICNwYXRpZW50PW5jZGNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpW2NvbG5hbWVzKGdtKV0sCiAgIyBzcGVjaWZ5IGNvbG9ycwogIGNvbCA9IGxpc3QoY2VsbHM9YW5ub3QucGFsLAogICAgICAgICAgICAgcGF0aWVudD1zYW1wbGUucGFsCiAgICAgICAgICAgICAjcHNldWRvdGltZT1jaXJjbGl6ZTo6Y29sb3JSYW1wMihjKC0xLCAwLCAxKSwgYygnZGFya2dyZWVuJywnZ3JleTkwJywnb3JhbmdlJykpCiAgKSwKICBib3JkZXIgPSBUCikKcmEgPC0gQ29tcGxleEhlYXRtYXA6OkhlYXRtYXBBbm5vdGF0aW9uKGRmPWRhdGEuZnJhbWUoY2x1c3Q9YXMuZmFjdG9yKGdtLmhjbC5jbHVzdFtyb3duYW1lcyhnbSldKSksd2hpY2g9J3Jvdycsc2hvd19hbm5vdGF0aW9uX25hbWU9RkFMU0UsIHNob3dfbGVnZW5kPUZBTFNFLCBib3JkZXI9VCkKCmhtIDwtIEhlYXRtYXAoZ20sIGNsdXN0ZXJfY29sdW1ucyA9IEYsIGNsdXN0ZXJfcm93cyA9IEYsc2hvd19jb2x1bW5fbmFtZXMgPSBGLGJvcmRlcj1ULCB0b3BfYW5ub3RhdGlvbiA9IGhhLCBuYW1lPSdleHByZXNzaW9uJywgc2hvd19oZWF0bWFwX2xlZ2VuZCA9IEYsIHNob3dfcm93X2RlbmQgPSBGLCBzaG93X3Jvd19uYW1lcz1GLCBsZWZ0X2Fubm90YXRpb24gPSByYSkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9Nn0KbGFiZWxlZC5nZW5lcyA8LSByb3duYW1lcyhnbSlbcm91bmQoc2VxKDEsbnJvdyhnbSksbGVuZ3RoLm91dCA9IDcwKSldOyAKaG0gKyBDb21wbGV4SGVhdG1hcDo6cm93QW5ub3RhdGlvbihsaW5rID0gQ29tcGxleEhlYXRtYXA6OmFubm9fbWFyayhhdCA9IG1hdGNoKGxhYmVsZWQuZ2VuZXMscm93bmFtZXMoZ20pKSwgbGFiZWxzID0gbGFiZWxlZC5nZW5lcywgbGFiZWxzX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDcpKSkKYGBgCgoKYGBge3J9CmduczIgPC0gYygnUEhPWDJCJywnUkdTNScsJ01MTFQxMScsIlNUTU4yIiwgJ1BDQlAyJywgJ01ESycsJ0hNR0IxJywnU1JQMTQnLCAjJ0NBTE0yJywKICAgICAgICAgIydOTFJQMScsCiAgICAgICAgICdBQkNBOCcsJ0ZYWUQxJywnQ0RIMTknLCdFUkJCMycsJ1MxMDBCJywnU09YMTAnLCdNUFonLCdTMTAwQTEwJywnQjJNJywnSUZJVE0zJywgIyAnQ05OMycsCiAgICAgICAgICdQT0RYTCcsJ05QTlQnLCAnVE5OVDInLCdGR0YxJywKICAgICAgICAgJ01BRkYnLCdLTEY0JywnTVlDJywnRk9TQicsCiAgICAgICAgICdQUlJYMScsJ0NBTEQxJywnTFVNJywnUERHRlJBJywnQ09MMUEyJywnQkdOJykKI2duczIgPC0gcm93bmFtZXMoZ20pW1JlZHVjZShzZXEsbWF0Y2goYygiQU5YQTEiLCJQU0FQIikscm93bmFtZXMoZ20pKSldCiNnbnMyIDwtIHJvd25hbWVzKGdtKVtSZWR1Y2Uoc2VxLG1hdGNoKGMoIlRNU0IxNUEiLCJBUEMiKSxyb3duYW1lcyhnbSkpKV0KZ20yIDwtIHpvbzo6cm9sbGFwcGx5KGFzLm1hdHJpeCh0KG1hdFtyZXYoZ25zMiksXSkpLDMwLG1lYW4sYWxpZ249J2xlZnQnLHBhcnRpYWw9VCkgJT4lIGFwcGx5KDIscmVzY2FsZS5hbmQuY2VudGVyLG1heC5xdWFudGlsZT0xLTVlLTMpICU+JSB0CiNnbSA8LSBhcHBseShtYXRbZ25zLF0sMSxyZXNjYWxlLmFuZC5jZW50ZXIpICU+JSB6b286OnJvbGxhcHBseSgxMCxtZWFuLGFsaWduPSdsZWZ0JyxwYXJ0aWFsPVQpICU+JSB0CmNvbG5hbWVzKGdtMikgPC0gY29sbmFtZXMobWF0KQpgYGAKCgoKYGBge3IgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NH0KcmVxdWlyZShDb21wbGV4SGVhdG1hcCkKaGEgPSBIZWF0bWFwQW5ub3RhdGlvbigKICBjZWxscz1hbm5vdFtjb2xuYW1lcyhnbTIpXSwgIAogICNwYXRpZW50PW5jZGNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpW2NvbG5hbWVzKGdtMildLAogICMgc3BlY2lmeSBjb2xvcnMKICBjb2wgPSBsaXN0KGNlbGxzPWFubm90LnBhbCwKICAgICAgICAgICAgIHBhdGllbnQ9c2FtcGxlLnBhbAogICAgICAgICAgICAgI3BzZXVkb3RpbWU9Y2lyY2xpemU6OmNvbG9yUmFtcDIoYygtMSwgMCwgMSksIGMoJ2RhcmtncmVlbicsJ2dyZXk5MCcsJ29yYW5nZScpKQogICksCiAgYm9yZGVyID0gVAopCgpobTIgPC0gSGVhdG1hcChnbTIsIGNsdXN0ZXJfY29sdW1ucyA9IEYsIGNsdXN0ZXJfcm93cyA9IEYsc2hvd19jb2x1bW5fbmFtZXMgPSBGLGJvcmRlcj1ULCB0b3BfYW5ub3RhdGlvbiA9IGhhLCBuYW1lPSdleHByZXNzaW9uJywgc2hvd19oZWF0bWFwX2xlZ2VuZCA9IEYsIHNob3dfcm93X2RlbmQgPSBGLCByb3dfbmFtZXNfZ3AgPSBncmlkOjpncGFyKGZvbnRzaXplID0gOCksdXNlX3Jhc3Rlcj1ULCByYXN0ZXJfZGV2aWNlID0gIkNhaXJvUE5HIikKaG0yCmBgYAoKYGBge3J9CnBkZihmaWxlPSdzbWFsbC5icmlkZ2UuaGVhdG1hcC5wZGYnLHdpZHRoPTQsaGVpZ2h0PTQpOyBobTI7IGRldi5vZmYoKQpDYWlybzo6Q2Fpcm9QTkcoZmlsZT0nc21hbGwuYnJpZGdlLmhlYXRtYXAucG5nJyx3aWR0aD00MDAsaGVpZ2h0PTQwMCk7IGhtMjsgZGV2Lm9mZigpCmBgYAoKQ29tYmluZWQgbGFyZ2UgaGVhdG1hcApgYGB7cn0KZ25zIDwtIHB0LmdlbmVzJGdlbmVbcHQuZ2VuZXMkQT4xZS0yXVsxOjEwMDBdCmducyA8LSB1bmlxdWUoYyhnbnMsZ25zMikpCmBgYAoKYGBge3J9CmdtIDwtIHpvbzo6cm9sbGFwcGx5KGFzLm1hdHJpeCh0KG1hdFtyZXYoZ25zKSxdKSksMzAsbWVhbixhbGlnbj0nbGVmdCcscGFydGlhbD1UKSAlPiUgYXBwbHkoMixyZXNjYWxlLmFuZC5jZW50ZXIsbWF4LnF1YW50aWxlPTEtMWUtMykgJT4lIHQKY29sbmFtZXMoZ20pIDwtIGNvbG5hbWVzKG1hdCkKIyBjbHVzdGVyCiNnbS5oY2wgPC0gaGNsdXN0KGFzLmRpc3QoMi1jb3IodChnbSkpKSxtZXRob2Q9J3dhcmQuRDInKQpnbS5oY2wgPC0gaGNsdXN0KGRpc3QoZ20pLG1ldGhvZD0nd2FyZC5EMicpCmdtLmhjbC5jbHVzdCA8LSBjdXRyZWUoZ20uaGNsLDQwKQpnbSA8LSBnbVtnbS5oY2wkb3JkZXIsXQpgYGAKCgpgYGB7ciB9CnJlcXVpcmUoQ29tcGxleEhlYXRtYXApCmhhID0gSGVhdG1hcEFubm90YXRpb24oCiAgY2VsbHM9YW5ub3RbY29sbmFtZXMoZ20pXSwgIAogICNwYXRpZW50PW5jZGNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpW2NvbG5hbWVzKGdtKV0sCiAgIyBzcGVjaWZ5IGNvbG9ycwogIGNvbCA9IGxpc3QoY2VsbHM9YW5ub3QucGFsLAogICAgICAgICAgICAgcGF0aWVudD1zYW1wbGUucGFsCiAgICAgICAgICAgICAjcHNldWRvdGltZT1jaXJjbGl6ZTo6Y29sb3JSYW1wMihjKC0xLCAwLCAxKSwgYygnZGFya2dyZWVuJywnZ3JleTkwJywnb3JhbmdlJykpCiAgKSwKICBib3JkZXIgPSBUCikKcmEgPC0gQ29tcGxleEhlYXRtYXA6OkhlYXRtYXBBbm5vdGF0aW9uKGRmPWRhdGEuZnJhbWUoY2x1c3Q9YXMuZmFjdG9yKGdtLmhjbC5jbHVzdFtyb3duYW1lcyhnbSldKSksd2hpY2g9J3Jvdycsc2hvd19hbm5vdGF0aW9uX25hbWU9RkFMU0UsIHNob3dfbGVnZW5kPUZBTFNFLCBib3JkZXI9VCkKCmhtIDwtIEhlYXRtYXAoZ20sIGNsdXN0ZXJfY29sdW1ucyA9IEYsIGNsdXN0ZXJfcm93cyA9IEYsc2hvd19jb2x1bW5fbmFtZXMgPSBGLGJvcmRlcj1ULCB0b3BfYW5ub3RhdGlvbiA9IGhhLCBuYW1lPSdleHByZXNzaW9uJywgc2hvd19oZWF0bWFwX2xlZ2VuZCA9IEYsIHNob3dfcm93X2RlbmQgPSBGLCBzaG93X3Jvd19uYW1lcz1GLHVzZV9yYXN0ZXI9VCwgcmFzdGVyX2RldmljZSA9ICJDYWlyb1BORyIpCmBgYAoKYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NH0KbGFiZWxlZC5nZW5lcyA8LSByb3duYW1lcyhnbSlbcm91bmQoc2VxKDEsbnJvdyhnbSksbGVuZ3RoLm91dCA9IDcwKSldOyAjCmxhYmVsZWQuZ2VuZXMgPC0gZ25zMjsKaG0gPC0gaG0gKyBDb21wbGV4SGVhdG1hcDo6cm93QW5ub3RhdGlvbihsaW5rID0gQ29tcGxleEhlYXRtYXA6OmFubm9fbWFyayhhdCA9IG1hdGNoKGxhYmVsZWQuZ2VuZXMscm93bmFtZXMoZ20pKSwgbGFiZWxzID0gbGFiZWxlZC5nZW5lcywgbGFiZWxzX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDcpKSkKaG0KYGBgCmBgYHtyfQpwZGYoZmlsZT0nZnVsbC5icmlkZ2UuaGVhdG1hcC5wZGYnLHdpZHRoPTMuNSxoZWlnaHQ9NSk7IGhtOyBkZXYub2ZmKCkKQ2Fpcm9QTkcoZmlsZT0nZnVsbC5icmlkZ2UuaGVhdG1hcC5wbmcnLHdpZHRoPTM1MCxoZWlnaHQ9NTAwKTsgaG07IGRldi5vZmYoKQpgYGAKCgpQc2V1ZG90aW1lIHRlc3RzCgpgYGB7ciBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD00fQpwdHIgPC0gcmFuayhwdCkgIyB3b3JrIG9uIHJhbmtzIC0gdGhleSdyZSBtb3JlIHN0YWJsZQpkZiA8LSBkYXRhLmZyYW1lKHB0PXB0cixhbm5vdD1hbm5vdFtuYW1lcyhwdHIpXSkKZ2dwbG90KGRmLGFlcyh4PXB0LGNvbG9yPWFubm90KSkgKyBnZW9tX2RlbnNpdHkoKSArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoMS4yZTMsMS41ZTMsMi4wZTMsMi42ZTMpKQpgYGAKCgoKVGVzdCBmb3IgZGV2aWF0aW9ucyBmcm9tIGxpbmVhciBpbnRlcnBvbGF0aW9uIGJldHdlZW4gdHdvIGNsdXN0ZXJzIG9uIHRoZSBwcmluY2lwYWwgY3VydmUKYGBge3J9CiMgcmV0dXJucyBub3JtYWxpemVkIG1lYW4gcmVzaWR1YWxzIHBlciBnZW5lIHdpdGhpbiB0aGUgY2VsMyBwb3B1bGF0aW9uLCByZWxhdGl2ZSB0byBhIHNpbXBsZSBsaW5lYXIgbW9kZWwgbWl4aW5nIGNlbDEgYW5kIGNlbDIgcG9wdWxhdGlvbnMKIyBjZWwxLGNlbDIsY2VsMyAtIGNlbGwgbmFtZSB2ZWN0b3JzCiMgbWF0IC0gbWF0cml4IG9mIG1vbGVjdWxlIGNvdW50cyAocmF3IGNvdW50cyAtIG5vdCBub3JtYWxpemVkKQpsaW4uZGV2IDwtIGZ1bmN0aW9uKGNlbDEsY2VsMixjZWwzLG1hdCkgewogIG1hdCA8LSBtYXRbcm93bmFtZXMobWF0KSAlaW4lIGMoY2VsMSxjZWwyLGNlbDMpLCxkcm9wPUZdCiAgY2YgPC0gc2V0TmFtZXMoYXMuZmFjdG9yKHJlcChjKCdnMScsJ2cyJyksYyhsZW5ndGgoY2VsMSksbGVuZ3RoKGNlbDIpKSkpLGMoY2VsMSxjZWwyKSkKICAjIGNhbGN1bGF0ZSBhZ2dyZWdhdGUgY291bnQgcHJvZmlsZXMKICBjbSA8LSBjb25vczo6OmNvbGxhcHNlQ2VsbHNCeVR5cGUobWF0LGNmKQogIGNtMSA8LSBjbVsxLF07IGNtMiA8LSBjbVsyLF0KICAjIGZpdCBsaW5lYXIgbW9kZWwgb24gdGhlIGJyaWRnZQogIHggPC0gYXMubWF0cml4KHQobWF0W2NlbDMsXSkpCiAgeSA8LSBsbSh4fmNtMStjbTItMSkKICAjIHJlcG9ydCByZXNpZHVhbHMKICAjcnMgPC0gcnN0dWRlbnQoeSkKICAjIGNhbGN1bGF0ZSBwZWFyc29uIHJlc2lkdWFsIHVzaW5nIHNkIGZyb20gYWxsIHRocmVlIHBvcHVsYXRpb25zIChpbnN0ZWFkIG9mIGEgc3RhbmRhcmQgc3R1ZGVudGl6ZWQgcmVzaXVkYWwpCiAgcnMgPC0gcmVzaWR1YWxzKHksJ3Jlc3BvbnNlJykKICByc3MgPC0gcm93TWVhbnMocnMpCiAgcnNzIDwtIHJzcy9hcHBseShtYXQsMixzZCkgIyBhbGwgdGhyZWUgcG9wdWxhdGlvbnMKICByc3MgPC0gcnNzW2lzLmZpbml0ZShyc3MpXQogIHJzcyA8LSByc3Nbb3JkZXIocnNzLGRlY3JlYXNpbmc9VCldCn0KCiMgYSBjb252ZW5pZW5jZSB3cmFwcGVyLCBzdGFydGluZyB3aXRoIGEgcHNldWRvdGltZSwgY29ub3Mgb2JqZWN0IGFuZCB0aGUgc3RhcnQvZW5kIHJlZ2lvbiBwc2V1ZG90aW1lIHJhbmdlcyAocmVnMSxyZWcyIGFyZSB0dXBsZXMgb2YgdGltZSB2YWx1ZXMpCmxpbi5kZXYucHQgPC0gZnVuY3Rpb24ocHQsY29uLHJlZzEscmVnMikgewogICMgZGV0ZXJtaW5lIGNlbGxzIGFzc29jaWF0ZWQgd2l0aCB0aGUgdHdvIHJlZ2lvbnMKICBjZWwxIDwtIG5hbWVzKHB0KVtwdD49cmVnMVsxXSAmIHB0PD1yZWcxWzJdXQogIGNlbDIgPC0gbmFtZXMocHQpW3B0Pj1yZWcyWzFdICYgcHQ8PXJlZzJbMl1dCiAgY2VsMyA8LSBuYW1lcyhwdClbcHQ+cmVnMVsyXSAmIHB0PHJlZzJbMV1dCiAgCiAgIyBjb25zdHJ1Y3QgYSBqb2ludCBjb3VudCBtYXRyaXgKICBtYXQgPC0gY29ub3M6OjpyYXdNYXRyaWNlc1dpdGhDb21tb25HZW5lcyhjb24pCiAgbWF0IDwtIGRvLmNhbGwocmJpbmQsbGFwcGx5KG1hdCxmdW5jdGlvbih4KSB4W3Jvd25hbWVzKHgpICVpbiUgYyhjZWwxLGNlbDIsY2VsMyksLGRyb3A9Rl0pKQogIGxpbi5kZXYoY2VsMSxjZWwyLGNlbDMsbWF0KQp9CmBgYAoKCmBgYHtyfQpyc3MgPC0gbGluLmRldi5wdChwdHIsbmNkY29uLGMoMCwxLjJlMyksYygyLjZlMyw2ZTMpKQpgYGAKCmBgYHtyfQpyc3MyIDwtIGxpbi5kZXYucHQocHRyLG5jZGNvbixjKDAsMS4yZTMpLGMoMS41ZTMsMi4wZTMpKQpgYGAKCmBgYHtyfQpyc3MzIDwtIGxpbi5kZXYucHQocHRyLG5jZGNvbixjKDEuNWUzLDIuMGUzKSxjKDNlMyw2ZTMpKQpgYGAKCgoKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KbiA8LSAzMDsKZGYgPC0gZGF0YS5mcmFtZShnZW5lPW5hbWVzKHJzczIpWzE6bl0scj1yc3MyWzE6bl0pCnAxIDwtIGdncGxvdChkZixhZXMoeD1ucm93KGRmKS1yYW5rKHIpLHk9cixsYWJlbD1nZW5lKSkrZ2VvbV9wb2ludCgpK2dlb21fdGV4dF9yZXBlbCgpK3hsYWIoImdlbmUgcmFuayIpK3lsYWIoJ25vcm1hbGl6ZWQgcmVzaWR1YWwnKSt0aGVtZV9idygpCnBkZihmaWxlPSdtZXNlbmNoeW1hbC5icmlkZ2UucmVzaWR1YWxzLnBkZicsd2lkdGg9NSxoZWlnaHQ9NSk7IHByaW50KHAxKTsgZGV2Lm9mZigpOwpwMQpgYGAKCnNpbTI6IFBPRFhMIC4uLiBOUE5ULCBUTk5UMiBOUEhTMSwgTkRORiwgTU1FLCBHUk4/Ck1ZQywgWkZBTkQ1LCBNQUZGIHNob3cgbmljZSBmb2NpIGNsb3NlIHRvIHRoZSBicmlkZ2Ugb24gdGhlIGZpYnJvYmxhc3Qgc2lkZQoKYGBge3IgZmlnLndpZHRoPTcsZmlnLmhlaWdodD03fQpuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuMyxzaXplPTAuMyxnZW5lPSdQSE9YMkInLHBsb3QubmE9RixncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk5KQpgYGAKCgoKUnVuIEdTRUEKYGBge3J9CnNvdXJjZSgifi9rZWl0aC9tZTMvZ29zaW0uciIpCmxvYWQoIn4va2VpdGgvbWUzL29yZy5Icy5HT2VudnMuUkRhdGEiKQpgYGAKCmBgYHtyfQpwYyA8LSByc3MKZ3NsIDwtIGxzKGVudj1vcmcuSHMuR08yU3ltYm9sKQpnc2wubmcgPC0gdW5saXN0KGxhcHBseShzbihnc2wpLGZ1bmN0aW9uKGdvKSBzdW0oZ2V0KGdvLGVudj1vcmcuSHMuR08yU3ltYm9sKSAlaW4lIG5hbWVzKHBjKSkpKQpnc2wgPC0gZ3NsW2dzbC5uZz49MTAgJiBnc2wubmc8PTJlM10KYGBgCgpgYGB7cn0KdmFsIDwtIHJzcwpnc2VhLnJzcy5wMSA8LSBpdGVyYXRpdmUuYnVsay5nc2VhKHZhbHVlcz12YWwsc2V0Lmxpc3Q9bGFwcGx5KHNuKGdzbCksZ2V0LGVudj1vcmcuSHMuR08yU3ltYm9sKSxwb3dlcj0xLG1jLmNvcmVzPTMyKQpgYGAKCgojIyMgdXNpbmcgc2ltcGxlIFBQVApgYGB7cn0KcmVxdWlyZShjcmVzdHJlZSkKYGBgCgpTdGFydCB3aXRoIGEgaGlnaC1kaW1lbnNpb25hbCBlbWJlZGRpbmcKYGBge3J9Cm9sZC5lbWIgPC0gbmNkY29uJGVtYmVkZGluZzsKaGllbWIgPC0gbmNkY29uJG1pc2MkZW1iZWRkaW5ncyRsaW5maXQgPC0gY29uJGVtYmVkR3JhcGgobWV0aG9kPSdsYXJnZVZpcycsdGFyZ2V0LmRpbXM9NCkKbmNkY29uJGVtYmVkZGluZyA8LSBvbGQuZW1iOwpgYGAKCnJlc3RyaWN0IHRvIHRoZSBjZWxscyBvZiBpbnRlcmVzdApgYGB7cn0KaGllbWIgPC0gaGllbWJbcm93bmFtZXMoaGllbWIpJWluJSBjZWxscyxdCmVtYiA8LSBuY2Rjb24kZW1iZWRkaW5nW3Jvd25hbWVzKG5jZGNvbiRlbWJlZGRpbmcpICVpbiUgY2VsbHMsXQpgYGAKCgoKYGBge3J9CnB0cmVlIDwtIHBwdC50cmVlKFg9dChoaWVtYiksZW1iPU5BLGxhbWJkYT0xMDAsc2lnbWE9MC41LG1ldHJpY3M9J2V1Y2xpZGVhbicsTT0zMDAscGxvdD1GLG91dHB1dCA9IEYpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD01LGZpZy5oZWlnaHQ9NX0KcGxvdHBwdChwdHJlZSxlbWIsY2V4LnRyZWU9MC4xLGNleC5tYWluPTAuMixsd2QudHJlZT0xLHRpcHM9VCkKYGBgCgpgYGB7cn0KcHRyZWUgPC0gc2V0cm9vdChwdHJlZSwyNTkpCmBgYAoKCgojIyMgRVJCQjMgYW5kIE5SRzEKCmBgYHtyIGZpZy53aWR0aD05LGZpZy5oZWlnaHQ9M30KZ25zIDwtIGMoIkVSQkIzIiwiTlJHMSIpOyAKcDAgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjEsc2l6ZT0wLjEsZ3JvdXBzPWFubm90LHBhbGV0dGU9YW5ub3QucGFsLHBsb3QubmE9RixyYXN0ZXI9VCxyYXN0ZXIud2lkdGg9NCxyYXN0ZXIuaGVpZ2h0PTQpCnBsIDwtIGxhcHBseShnbnMsZnVuY3Rpb24oZykgbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjIsc2l6ZT0wLjEsZ2VuZT1nLHBsb3QubmE9RixncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk5LHJhc3Rlcj1ULHJhc3Rlci53aWR0aD00LHJhc3Rlci5oZWlnaHQ9NCkgK2dlb21fbGFiZWwoeD1JbmYseT1JbmYsdmp1c3Q9MSxoanVzdD0xLGxhYmVsPWcsZGF0YT1kYXRhLmZyYW1lKHg9YygxKSksbGFiZWwuc2l6ZT0wKSkKcHAgPC0gcGxvdF9ncmlkKHBsb3RsaXN0PWMobGlzdChwMCkscGwpLG5yb3c9MSkKcGRmKCJlcmJiM19ucmcxLnBkZiIsd2lkdGg9OSxoZWlnaHQ9Myk7IHByaW50KHBwKTsgZGV2Lm9mZigpOwpwcApgYGAKCgojIyMgTWVzZW5jaHltYWwKCgpBc3BlY3QgcmF0aW9uIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgc2VsZWN0ZWQgc3Vic2V0IGFuZCB0aGUgYWN0dWFsIG9uZQpgYGB7cn0KUmVkdWNlKGV2YWwoJy8nKSxhcHBseShuY2Rjb24kZW1iZWRkaW5nLDIsZnVuY3Rpb24oeCkgZGlmZihyYW5nZSh4KSkpKS9SZWR1Y2UoZXZhbCgnLycpLGFwcGx5KG5jZGNvbiRlbWJlZGRpbmdbbWVzLmNlbGxzLF0sMixmdW5jdGlvbih4KSBkaWZmKHJhbmdlKHgpKSkpCmBgYAoKYGBge3IgZmlnLmhlaWdodD0wLjUyLGZpZy53aWR0aD0xfQpwMCA8LSBuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuMixzaXplPTAuMixncm91cHM9YW5ub3RbbWVzLmNlbGxzXSxwYWxldHRlPWFubm90LnBhbCxtYXJrLmdyb3Vwcz1GLHBsb3QubmE9RixncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk1LHJhc3Rlcj1ULHJhc3Rlci53aWR0aD0yLHJhc3Rlci5oZWlnaHQ9MSkgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsgCiAgI2dlb21fbGFiZWwoeD0tSW5mLHk9SW5mLHZqdXN0PTEsaGp1c3Q9MCxsYWJlbD0nY2x1c3RlcnMnLGRhdGE9ZGF0YS5mcmFtZSh4PWMoMSkpLGxhYmVsLnNpemU9MCkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCkpCnAwCmBgYAoKYGBge3IgZmlnLmhlaWdodD0wLjUyLGZpZy53aWR0aD0xfQpuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuNSxzaXplPTAuNSxjb2xvcnM9Y29ub3M6OjpnZXRHZW5lRXhwcmVzc2lvbihuY2Rjb24sIlBPRFhMIilbbWVzLmNlbGxzXSxtYXJrLmdyb3Vwcz1GLHBsb3QubmE9RixncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk5LHJhc3Rlcj1ULHJhc3Rlci53aWR0aD0zLHJhc3Rlci5oZWlnaHQ9MS41KSsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArIAogICNnZW9tX2xhYmVsKHg9LUluZix5PUluZix2anVzdD0xLGhqdXN0PTAsbGFiZWw9J2NsdXN0ZXJzJyxkYXRhPWRhdGEuZnJhbWUoeD1jKDEpKSxsYWJlbC5zaXplPTApICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTIuMDYsZmlnLndpZHRoPTF9CmducyA8LSBjKCJQREdGUkEiLCJNWUMiLCJLTEY0IikKcGwgPC0gbGFwcGx5KGducyxmdW5jdGlvbihnKSBuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuMixzaXplPTAuMixjb2xvcnM9Y29ub3M6OjpnZXRHZW5lRXhwcmVzc2lvbihuY2Rjb24sZylbbWVzLmNlbGxzXSxtYXJrLmdyb3Vwcz1GLHBsb3QubmE9RixncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjk1LHJhc3Rlcj1ULHJhc3Rlci53aWR0aD0yLHJhc3Rlci5oZWlnaHQ9MSkgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsgCiAgZ2VvbV9sYWJlbCh4PUluZix5PS1JbmYsdmp1c3Q9MCxoanVzdD0xLGxhYmVsPWcsZGF0YT1kYXRhLmZyYW1lKHg9YygxKSksbGFiZWwuc2l6ZT0wKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSkpCnBwIDwtIHBsb3RfZ3JpZChwbG90bGlzdD1jKGxpc3QocDApLHBsKSxuY29sPTEpCgptdWx0IDwtIDEuNTsgcGRmKGZpbGU9J21lcy5leHByLnBkZicsd2lkdGg9MSptdWx0LGhlaWdodD1sZW5ndGgocGwpKjAuNTgqbXVsdCk7IHByaW50KHBwKTsgZGV2Lm9mZigpCnBwCmBgYA==